diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-21 02:05:41 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-21 02:05:41 +0000 |
commit | 67adc70df0415c97aa573db3054a526125ee9dca (patch) | |
tree | a1a981f65c0ac1744f46c2f05f7777419127a86e /src/com/android/launcher3 | |
parent | 99a8d2f1017536e08c8e84d513495db308a8b7b5 (diff) | |
parent | 793f5c518656f94732be695eac4d5b01fa50ae01 (diff) | |
download | android_packages_apps_Trebuchet-67adc70df0415c97aa573db3054a526125ee9dca.tar.gz android_packages_apps_Trebuchet-67adc70df0415c97aa573db3054a526125ee9dca.tar.bz2 android_packages_apps_Trebuchet-67adc70df0415c97aa573db3054a526125ee9dca.zip |
Snap for 6021960 from 793f5c518656f94732be695eac4d5b01fa50ae01 to qt-qpr2-release
Change-Id: Iffd470dfbee1606aeaecfd7bca0b7c27088d76fa
Diffstat (limited to 'src/com/android/launcher3')
129 files changed, 3392 insertions, 2543 deletions
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 65f9d6bc0..af2cdc3ed 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -16,7 +16,6 @@ package com.android.launcher3; -import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; @@ -30,6 +29,7 @@ import android.util.AttributeSet; import android.util.Pair; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.LinearLayout; import androidx.annotation.IntDef; @@ -86,7 +86,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch // Type of popups which should be kept open during launcher rebind public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET - | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE; + | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE; // Usually we show the back button when a floating view is open. Instead, hide for these types. public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE @@ -171,7 +171,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second); if (mIsOpen) { - sendAccessibilityEvent(TYPE_VIEW_FOCUSED); + performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); } ActivityContext.lookupContext(getContext()).getDragLayer() .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED); diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index d884049f5..c8e7619c7 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -29,11 +29,19 @@ import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageManagerHelper; +import java.util.Comparator; + /** * Represents an app in AllAppsView. */ public class AppInfo extends ItemInfoWithIcon { + public static AppInfo[] EMPTY_ARRAY = new AppInfo[0]; + public static Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> { + int uc = a.user.hashCode() - b.user.hashCode(); + return uc != 0 ? uc : a.componentName.compareTo(b.componentName); + }; + /** * The intent used to start the application. */ @@ -41,6 +49,9 @@ public class AppInfo extends ItemInfoWithIcon { public ComponentName componentName; + // Section name used for indexing. + public String sectionName = ""; + public AppInfo() { itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; } @@ -74,6 +85,8 @@ public class AppInfo extends ItemInfoWithIcon { componentName = info.componentName; title = Utilities.trim(info.title); intent = new Intent(info.intent); + user = info.user; + runtimeStatusFlags = info.runtimeStatusFlags; } @Override @@ -116,4 +129,9 @@ public class AppInfo extends ItemInfoWithIcon { info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON; } } + + @Override + public AppInfo clone() { + return new AppInfo(this); + } } diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 9724869bf..ac4396778 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -38,11 +38,15 @@ import android.util.Pair; import android.util.Patterns; import android.util.Xml; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherProvider.SqlArguments; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Thunk; import org.xmlpull.v1.XmlPullParser; @@ -72,7 +76,7 @@ public class AutoInstallsLayout { static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost, LayoutParserCallback callback) { - Pair<String, Resources> customizationApkInfo = Utilities.findSystemApk( + Pair<String, Resources> customizationApkInfo = PackageManagerHelper.findSystemApk( ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager()); if (customizationApkInfo == null) { return null; @@ -83,7 +87,7 @@ public class AutoInstallsLayout { // Try with grid size and hotseat count String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT, - grid.numColumns, grid.numRows, grid.numHotseatIcons); + grid.numColumns, grid.numRows, grid.numHotseatIcons); int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg); // Try with only grid size @@ -91,7 +95,7 @@ public class AutoInstallsLayout { Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying layout without hosteat"); layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES, - grid.numColumns, grid.numRows); + grid.numColumns, grid.numRows); layoutId = targetRes.getIdentifier(layoutName, "xml", pkg); } @@ -116,6 +120,7 @@ public class AutoInstallsLayout { private static final String TAG_AUTO_INSTALL = "autoinstall"; private static final String TAG_FOLDER = "folder"; private static final String TAG_APPWIDGET = "appwidget"; + protected static final String TAG_SEARCH_WIDGET = "searchwidget"; private static final String TAG_SHORTCUT = "shortcut"; private static final String TAG_EXTRA = "extra"; @@ -147,8 +152,10 @@ public class AutoInstallsLayout { private static final String HOTSEAT_CONTAINER_NAME = Favorites.containerToString(Favorites.CONTAINER_HOTSEAT); - @Thunk final Context mContext; - @Thunk final AppWidgetHost mAppWidgetHost; + @Thunk + final Context mContext; + @Thunk + final AppWidgetHost mAppWidgetHost; protected final LayoutParserCallback mCallback; protected final PackageManager mPackageManager; @@ -160,7 +167,8 @@ public class AutoInstallsLayout { private final int mColumnCount; private final int[] mTemp = new int[2]; - @Thunk final ContentValues mValues; + @Thunk + final ContentValues mValues; protected final String mRootTag; protected SQLiteDatabase mDb; @@ -244,7 +252,7 @@ public class AutoInstallsLayout { */ protected int parseAndAddNode( XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException { if (TAG_INCLUDE.equals(parser.getName())) { final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0); @@ -315,6 +323,7 @@ public class AutoInstallsLayout { parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser()); parsers.put(TAG_FOLDER, new FolderParser()); parsers.put(TAG_APPWIDGET, new PendingWidgetParser()); + parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser()); parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes)); return parsers; } @@ -347,15 +356,15 @@ public class AutoInstallsLayout { info = mPackageManager.getActivityInfo(cn, 0); } catch (PackageManager.NameNotFoundException nnfe) { String[] packages = mPackageManager.currentToCanonicalPackageNames( - new String[] { packageName }); + new String[]{packageName}); cn = new ComponentName(packages[0], className); info = mPackageManager.getActivityInfo(cn, 0); } final Intent intent = new Intent(Intent.ACTION_MAIN, null) - .addCategory(Intent.CATEGORY_LAUNCHER) - .setComponent(cn) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(cn) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); return addShortcut(info.loadLabel(mPackageManager).toString(), intent, Favorites.ITEM_TYPE_APPLICATION); @@ -393,10 +402,10 @@ public class AutoInstallsLayout { mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON); final Intent intent = new Intent(Intent.ACTION_MAIN, null) - .addCategory(Intent.CATEGORY_LAUNCHER) - .setComponent(new ComponentName(packageName, className)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(new ComponentName(packageName, className)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); return addShortcut(mContext.getString(R.string.package_state_unknown), intent, Favorites.ITEM_TYPE_APPLICATION); } @@ -444,7 +453,7 @@ public class AutoInstallsLayout { mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); return addShortcut(mSourceRes.getString(titleResId), intent, Favorites.ITEM_TYPE_SHORTCUT); } @@ -469,12 +478,22 @@ public class AutoInstallsLayout { */ protected class PendingWidgetParser implements TagParser { - @Override - public int parseAndAdd(XmlPullParser parser) - throws XmlPullParserException, IOException { + @Nullable + public ComponentName getComponentName(XmlPullParser parser) { final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); final String className = getAttributeValue(parser, ATTR_CLASS_NAME); if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) { + return null; + } + return new ComponentName(packageName, className); + } + + + @Override + public int parseAndAdd(XmlPullParser parser) + throws XmlPullParserException, IOException { + ComponentName cn = getComponentName(parser); + if (cn == null) { if (LOGD) Log.d(TAG, "Skipping invalid <appwidget> with no component"); return -1; } @@ -505,16 +524,15 @@ public class AutoInstallsLayout { throw new RuntimeException("Widgets can contain only extras"); } } - - return verifyAndInsert(new ComponentName(packageName, className), extras); + return verifyAndInsert(cn, extras); } protected int verifyAndInsert(ComponentName cn, Bundle extras) { mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); mValues.put(Favorites.RESTORED, - LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | - LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | - LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); + LauncherAppWidgetInfo.FLAG_ID_NOT_VALID + | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY + | LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); mValues.put(Favorites._ID, mCallback.generateNewItemId()); if (!extras.isEmpty()) { mValues.put(Favorites.INTENT, new Intent().putExtras(extras).toUri(0)); @@ -529,6 +547,23 @@ public class AutoInstallsLayout { } } + protected class SearchWidgetParser extends PendingWidgetParser { + @Override + @Nullable + public ComponentName getComponentName(XmlPullParser parser) { + return QsbContainerView.getSearchComponentName(mContext); + } + + @Override + protected int verifyAndInsert(ComponentName cn, Bundle extras) { + mValues.put(Favorites.OPTIONS, LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET); + int flags = mValues.getAsInteger(Favorites.RESTORED) + | WorkspaceItemInfo.FLAG_RESTORE_STARTED; + mValues.put(Favorites.RESTORED, flags); + return super.verifyAndInsert(cn, extras); + } + } + protected class FolderParser implements TagParser { private final ArrayMap<String, TagParser> mFolderElements; @@ -548,7 +583,7 @@ public class AutoInstallsLayout { if (titleResId != 0) { title = mSourceRes.getString(titleResId); } else { - title = mContext.getResources().getString(R.string.folder_name); + title = ""; } mValues.put(Favorites.TITLE, title); @@ -680,7 +715,8 @@ public class AutoInstallsLayout { int insertAndCheck(SQLiteDatabase db, ContentValues values); } - @Thunk static void copyInteger(ContentValues from, ContentValues to, String key) { + @Thunk + static void copyInteger(ContentValues from, ContentValues to, String key) { to.put(key, from.getAsInteger(key)); } } diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index 64550560f..b28077f7c 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -27,6 +27,8 @@ import android.content.Intent; import android.content.res.Configuration; import android.view.ContextThemeWrapper; +import androidx.annotation.IntDef; + import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogUtils; @@ -44,8 +46,6 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.util.ArrayList; -import androidx.annotation.IntDef; - public abstract class BaseActivity extends Activity implements UserEventDelegate, LogStateProvider, ActivityContext { @@ -265,12 +265,13 @@ public abstract class BaseActivity extends Activity } } - protected void dumpMisc(PrintWriter writer) { - writer.println(" deviceProfile isTransposed=" + getDeviceProfile().isVerticalBarLayout()); - writer.println(" orientation=" + getResources().getConfiguration().orientation); - writer.println(" mSystemUiController: " + mSystemUiController); - writer.println(" mActivityFlags: " + mActivityFlags); - writer.println(" mForceInvisible: " + mForceInvisible); + protected void dumpMisc(String prefix, PrintWriter writer) { + writer.println(prefix + "deviceProfile isTransposed=" + + getDeviceProfile().isVerticalBarLayout()); + writer.println(prefix + "orientation=" + getResources().getConfiguration().orientation); + writer.println(prefix + "mSystemUiController: " + mSystemUiController); + writer.println(prefix + "mActivityFlags: " + mActivityFlags); + writer.println(prefix + "mForceInvisible: " + mForceInvisible); } public static <T extends BaseActivity> T fromContext(Context context) { diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index f0b3afd14..994ba65a4 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -30,17 +30,17 @@ import android.view.ActionMode; import android.view.View; import android.widget.Toast; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.shortcuts.DeepShortcutManager; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.uioverrides.DisplayRotationListener; import com.android.launcher3.uioverrides.WallpaperColorInfo; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Themes; -import androidx.annotation.Nullable; - /** * Extension of BaseActivity allowing support for drag-n-drop */ @@ -120,6 +120,10 @@ public abstract class BaseDraggingActivity extends BaseActivity public abstract View getRootView(); + public void returnToHomescreen() { + // no-op + } + public Rect getViewBounds(View v) { int[] pos = new int[2]; v.getLocationOnScreen(pos); @@ -135,7 +139,7 @@ public abstract class BaseDraggingActivity extends BaseActivity public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item, @Nullable String sourceContainer) { - if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { + if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); return false; } @@ -237,14 +241,14 @@ public abstract class BaseDraggingActivity extends BaseActivity protected void onDeviceProfileInitiated() { if (mDeviceProfile.isVerticalBarLayout()) { mRotationListener.enable(); - mDeviceProfile.updateIsSeascape(getWindowManager()); + mDeviceProfile.updateIsSeascape(this); } else { mRotationListener.disable(); } } private void onDeviceRotationChanged() { - if (mDeviceProfile.updateIsSeascape(getWindowManager())) { + if (mDeviceProfile.updateIsSeascape(this)) { reapplyUi(); } } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index b1132494a..7adb6a442 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -104,6 +104,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private Drawable mIcon; private final boolean mCenterVertically; + private final int mDisplay; + private final CheckLongPressHelper mLongPressHelper; private final StylusEventHelper mStylusEventHelper; private final float mSlop; @@ -133,6 +135,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @ViewDebug.ExportedProperty(category = "launcher") private boolean mDisableRelayout = false; + @ViewDebug.ExportedProperty(category = "launcher") + private final boolean mIgnorePaddingTouch; + private IconLoadRequest mIconLoadRequest; public BubbleTextView(Context context) { @@ -152,26 +157,32 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, R.styleable.BubbleTextView, defStyle, 0); mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false); - int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE); + mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE); final int defaultIconSize; - if (display == DISPLAY_WORKSPACE) { + if (mDisplay == DISPLAY_WORKSPACE) { DeviceProfile grid = mActivity.getWallpaperDeviceProfile(); setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); setCompoundDrawablePadding(grid.iconDrawablePaddingPx); defaultIconSize = grid.iconSizePx; - } else if (display == DISPLAY_ALL_APPS) { + mIgnorePaddingTouch = true; + } else if (mDisplay == DISPLAY_ALL_APPS) { DeviceProfile grid = mActivity.getDeviceProfile(); setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx); setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx); defaultIconSize = grid.allAppsIconSizePx; - } else if (display == DISPLAY_FOLDER) { + mIgnorePaddingTouch = true; + } else if (mDisplay == DISPLAY_FOLDER) { DeviceProfile grid = mActivity.getDeviceProfile(); setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx); setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx); defaultIconSize = grid.folderChildIconSizePx; + mIgnorePaddingTouch = true; } else { + // widget_selection or shortcut_popup defaultIconSize = mActivity.getDeviceProfile().iconSizePx; + mIgnorePaddingTouch = false; } + mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false); mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride, @@ -319,6 +330,15 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public boolean onTouchEvent(MotionEvent event) { + // ignore events if they happen in padding area + if (event.getAction() == MotionEvent.ACTION_DOWN && mIgnorePaddingTouch + && (event.getY() < getPaddingTop() + || event.getX() < getPaddingLeft() + || event.getY() > getHeight() - getPaddingBottom() + || event.getX() > getWidth() - getPaddingRight())) { + return false; + } + // Call the superclass onTouchEvent first, because sometimes it changes the state to // isPressed() on an ACTION_UP boolean result = super.onTouchEvent(event); @@ -564,7 +584,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mDotInfo = mActivity.getDotInfoForItem(itemInfo); boolean isDotted = mDotInfo != null; float newDotScale = isDotted ? 1f : 0; - mDotRenderer = mActivity.getDeviceProfile().mDotRenderer; + if (mDisplay == DISPLAY_ALL_APPS) { + mDotRenderer = mActivity.getDeviceProfile().mDotRendererAllApps; + } else { + mDotRenderer = mActivity.getDeviceProfile().mDotRendererWorkSpace; + } if (wasDotted || isDotted) { // Animate when a dot is first added or when it is removed. if (animate && (wasDotted ^ isDotted) && isShown()) { diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 09fb2446d..976ccd52e 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -2702,6 +2702,14 @@ public class CellLayout extends ViewGroup implements Transposable { } } + /** + * Sets the position to the provided point + */ + public void setXY(Point point) { + cellX = point.x; + cellY = point.y; + } + public String toString() { return "(" + this.cellX + ", " + this.cellY + ")"; } diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java index 75297f63b..af8559477 100644 --- a/src/com/android/launcher3/DefaultLayoutParser.java +++ b/src/com/android/launcher3/DefaultLayoutParser.java @@ -14,13 +14,16 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; + import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.util.Thunk; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.net.URISyntaxException; import java.util.List; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; /** * Implements the layout parser with rules for internal layouts and partner layouts. @@ -55,7 +58,8 @@ public class DefaultLayoutParser extends AutoInstallsLayout { return getFolderElementsMap(mSourceRes); } - @Thunk ArrayMap<String, TagParser> getFolderElementsMap(Resources res) { + @Thunk + ArrayMap<String, TagParser> getFolderElementsMap(Resources res) { ArrayMap<String, TagParser> parsers = new ArrayMap<>(); parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser()); parsers.put(TAG_SHORTCUT, new UriShortcutParser(res)); @@ -67,6 +71,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { ArrayMap<String, TagParser> parsers = new ArrayMap<>(); parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser()); parsers.put(TAG_APPWIDGET, new AppWidgetParser()); + parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser()); parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes)); parsers.put(TAG_RESOLVE, new ResolveParser()); parsers.put(TAG_FOLDER, new MyFolderParser()); @@ -229,7 +234,8 @@ public class DefaultLayoutParser extends AutoInstallsLayout { /** * A parser which adds a folder whose contents come from partner apk. */ - @Thunk class PartnerFolderParser implements TagParser { + @Thunk + class PartnerFolderParser implements TagParser { @Override public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException, @@ -255,7 +261,8 @@ public class DefaultLayoutParser extends AutoInstallsLayout { /** * An extension of FolderParser which allows adding items from a different xml. */ - @Thunk class MyFolderParser extends FolderParser { + @Thunk + class MyFolderParser extends FolderParser { @Override public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException, @@ -281,7 +288,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { mPackageManager.getReceiverInfo(cn, 0); } catch (Exception e) { String[] packages = mPackageManager.currentToCanonicalPackageNames( - new String[] { cn.getPackageName() }); + new String[]{cn.getPackageName()}); cn = new ComponentName(packages[0], cn.getClassName()); try { mPackageManager.getReceiverInfo(cn, 0); diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 883e8c642..bc6fa6e90 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -24,16 +24,20 @@ import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.Surface; -import android.view.WindowManager; + +import androidx.annotation.Nullable; import com.android.launcher3.CellLayout.ContainerType; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.IconNormalizer; +import com.android.launcher3.util.DefaultDisplay; public class DeviceProfile { public final InvariantDeviceProfile inv; + // IDP with no grid override values. + @Nullable private final InvariantDeviceProfile originalIdp; // Device properties public final boolean isTablet; @@ -111,6 +115,7 @@ public class DeviceProfile { // All apps public int allAppsCellHeightPx; + public int allAppsCellWidthPx; public int allAppsIconSizePx; public int allAppsIconDrawablePaddingPx; public float allAppsIconTextSizePx; @@ -129,13 +134,15 @@ public class DeviceProfile { private boolean mIsSeascape; // Notification dots - public DotRenderer mDotRenderer; + public DotRenderer mDotRendererWorkSpace; + public DotRenderer mDotRendererAllApps; public DeviceProfile(Context context, InvariantDeviceProfile inv, - Point minSize, Point maxSize, + InvariantDeviceProfile originalIDP, Point minSize, Point maxSize, int width, int height, boolean isLandscape, boolean isMultiWindowMode) { this.inv = inv; + this.originalIdp = inv; this.isLandscape = isLandscape; this.isMultiWindowMode = isMultiWindowMode; @@ -227,17 +234,33 @@ public class DeviceProfile { // Recalculate the available dimensions using the new hotseat size. updateAvailableDimensions(dm, res); } + + if (originalIDP != null) { + // Grid size change should not affect All Apps UI, so we use the original profile + // measurements here. + DeviceProfile originalProfile = isLandscape + ? originalIDP.landscapeProfile + : originalIDP.portraitProfile; + allAppsIconSizePx = originalProfile.iconSizePx; + allAppsIconTextSizePx = originalProfile.iconTextSizePx; + allAppsCellHeightPx = originalProfile.allAppsCellHeightPx; + allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx; + allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx; + } updateWorkspacePadding(); // This is done last, after iconSizePx is calculated above. - mDotRenderer = new DotRenderer(iconSizePx, IconShape.getShapePath(), + mDotRendererWorkSpace = new DotRenderer(iconSizePx, IconShape.getShapePath(), IconShape.DEFAULT_PATH_SIZE); + mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace : + new DotRenderer(allAppsIconSizePx, IconShape.getShapePath(), + IconShape.DEFAULT_PATH_SIZE); } public DeviceProfile copy(Context context) { Point size = new Point(availableWidthPx, availableHeightPx); - return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape, - isMultiWindowMode); + return new DeviceProfile(context, inv, originalIdp, size, size, widthPx, heightPx, + isLandscape, isMultiWindowMode); } public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) { @@ -248,8 +271,8 @@ public class DeviceProfile { // In multi-window mode, we can have widthPx = availableWidthPx // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles' // widthPx and heightPx values where it's needed. - DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y, - isLandscape, true); + DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize, + mwSize.x, mwSize.y, isLandscape, true); // If there isn't enough vertical cell padding with the labels displayed, hide the labels. float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx @@ -307,11 +330,16 @@ public class DeviceProfile { updateAvailableFolderCellDimensions(dm, res); } + /** + * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx, + * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, + * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. + */ private void updateIconSize(float scale, Resources res, DisplayMetrics dm) { // Workspace final boolean isVerticalLayout = isVerticalBarLayout(); - float invIconSizePx = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; - iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizePx, dm) * scale)); + float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; + iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale)); iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale); iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale); @@ -328,13 +356,13 @@ public class DeviceProfile { } cellWidthPx = iconSizePx + iconDrawablePaddingPx; - // All apps - allAppsIconTextSizePx = iconTextSizePx; allAppsIconSizePx = iconSizePx; + allAppsIconTextSizePx = iconTextSizePx; allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; allAppsCellHeightPx = getCellSize().y; + allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx; - if (isVerticalLayout) { + if (isVerticalBarLayout()) { // Always hide the Workspace text with vertical bar layout. adjustToHideWorkspaceLabels(); } @@ -375,14 +403,15 @@ public class DeviceProfile { Point totalWorkspacePadding = getTotalWorkspacePadding(); // Check if the icons fit within the available height. - float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize; - int maxHeight = availableHeightPx - totalWorkspacePadding.y - folderMargin; - float scaleY = maxHeight / usedHeight; + float contentUsedHeight = folderCellHeightPx * inv.numFolderRows; + int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize + - folderMargin; + float scaleY = contentMaxHeight / contentUsedHeight; // Check if the icons fit within the available width. - float usedWidth = folderCellWidthPx * inv.numFolderColumns; - int maxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin; - float scaleX = maxWidth / usedWidth; + float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns; + int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin; + float scaleX = contentMaxWidth / contentUsedWidth; float scale = Math.min(scaleX, scaleY); if (scale < 1f) { @@ -419,14 +448,18 @@ public class DeviceProfile { } public Point getCellSize() { + return getCellSize(inv.numColumns, inv.numRows); + } + + private Point getCellSize(int numColumns, int numRows) { Point result = new Point(); // Since we are only concerned with the overall padding, layout direction does // not matter. Point padding = getTotalWorkspacePadding(); result.x = calculateCellWidth(availableWidthPx - padding.x - - cellLayoutPaddingLeftRightPx * 2, inv.numColumns); + - cellLayoutPaddingLeftRightPx * 2, numColumns); result.y = calculateCellHeight(availableHeightPx - padding.y - - cellLayoutBottomPaddingPx, inv.numRows); + - cellLayoutBottomPaddingPx, numRows); return result; } @@ -542,11 +575,19 @@ public class DeviceProfile { } /** + * Returns true when the number of workspace columns and all apps columns differs. + */ + private boolean allAppsHasDifferentNumColumns() { + return inv.numAllAppsColumns != inv.numColumns; + } + + /** * Updates orientation information and returns true if it has changed from the previous value. */ - public boolean updateIsSeascape(WindowManager wm) { + public boolean updateIsSeascape(Context context) { if (isVerticalBarLayout()) { - boolean isSeascape = wm.getDefaultDisplay().getRotation() == Surface.ROTATION_270; + boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation + == Surface.ROTATION_270; if (mIsSeascape != isSeascape) { mIsSeascape = isSeascape; return true; diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index b747d62ae..e2b7b68a9 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -50,9 +50,9 @@ public class FolderInfo extends ItemInfo { /** * The apps and shortcuts */ - public ArrayList<WorkspaceItemInfo> contents = new ArrayList<WorkspaceItemInfo>(); + public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>(); - ArrayList<FolderListener> listeners = new ArrayList<FolderListener>(); + private ArrayList<FolderListener> mListeners = new ArrayList<>(); public FolderInfo() { itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER; @@ -72,10 +72,10 @@ public class FolderInfo extends ItemInfo { * Add an app or shortcut for a specified rank. */ public void add(WorkspaceItemInfo item, int rank, boolean animate) { - rank = Utilities.boundToRange(rank, 0, contents.size()); + rank = Utilities.boundToRange(rank, 0, contents.size() + 1); contents.add(rank, item); - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onAdd(item, rank); + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onAdd(item, rank); } itemsChanged(animate); } @@ -87,53 +87,37 @@ public class FolderInfo extends ItemInfo { */ public void remove(WorkspaceItemInfo item, boolean animate) { contents.remove(item); - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onRemove(item); + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onRemove(item); } itemsChanged(animate); } - public void setTitle(CharSequence title) { - this.title = title; - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onTitleChanged(title); - } - } - @Override public void onAddToDatabase(ContentWriter writer) { super.onAddToDatabase(writer); writer.put(LauncherSettings.Favorites.TITLE, title) .put(LauncherSettings.Favorites.OPTIONS, options); - } public void addListener(FolderListener listener) { - listeners.add(listener); + mListeners.add(listener); } public void removeListener(FolderListener listener) { - listeners.remove(listener); + mListeners.remove(listener); } public void itemsChanged(boolean animate) { - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onItemsChanged(animate); - } - } - - public void prepareAutoUpdate() { - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).prepareAutoUpdate(); + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onItemsChanged(animate); } } public interface FolderListener { public void onAdd(WorkspaceItemInfo item, int rank); public void onRemove(WorkspaceItemInfo item); - public void onTitleChanged(CharSequence title); public void onItemsChanged(boolean animate); - public void prepareAutoUpdate(); } public boolean hasOption(int optionFlag) { diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index 00acdcd2f..03ee707af 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -105,8 +105,7 @@ public class Hotseat extends CellLayout implements LogContainerProvider, Insetta @Override public boolean onTouchEvent(MotionEvent event) { - // Don't let if follow through to workspace - return true; + return event.getY() > getCellHeight(); } @Override diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index 89ec2a564..fe916028b 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; @@ -29,8 +31,6 @@ import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.os.Handler; -import android.os.Message; import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; @@ -68,9 +68,6 @@ import java.util.Set; public class InstallShortcutReceiver extends BroadcastReceiver { - private static final int MSG_ADD_TO_QUEUE = 1; - private static final int MSG_FLUSH_QUEUE = 2; - public static final int FLAG_ACTIVITY_PAUSED = 1; public static final int FLAG_LOADER_RUNNING = 2; public static final int FLAG_DRAG_AND_DROP = 4; @@ -103,66 +100,57 @@ public class InstallShortcutReceiver extends BroadcastReceiver { public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450; public static final int NEW_SHORTCUT_STAGGER_DELAY = 85; - private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) { - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ADD_TO_QUEUE: { - Pair<Context, PendingInstallShortcutInfo> pair = - (Pair<Context, PendingInstallShortcutInfo>) msg.obj; - String encoded = pair.second.encodeToString(); - SharedPreferences prefs = Utilities.getPrefs(pair.first); - Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); - strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1); - strings.add(encoded); - prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply(); - return; - } - case MSG_FLUSH_QUEUE: { - Context context = (Context) msg.obj; - LauncherModel model = LauncherAppState.getInstance(context).getModel(); - if (model.getCallback() == null) { - // Launcher not loaded - return; - } + @WorkerThread + private static void addToQueue(Context context, PendingInstallShortcutInfo info) { + String encoded = info.encodeToString(); + SharedPreferences prefs = Utilities.getPrefs(context); + Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); + strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1); + strings.add(encoded); + prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply(); + } - ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>(); - SharedPreferences prefs = Utilities.getPrefs(context); - Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); - if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); - if (strings == null) { - return; - } + @WorkerThread + private static void flushQueueInBackground(Context context) { + LauncherModel model = LauncherAppState.getInstance(context).getModel(); + if (model.getCallback() == null) { + // Launcher not loaded + return; + } - LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); - for (String encoded : strings) { - PendingInstallShortcutInfo info = decode(encoded, context); - if (info == null) { - continue; - } - - String pkg = getIntentPackage(info.launchIntent); - if (!TextUtils.isEmpty(pkg) - && !launcherApps.isPackageEnabledForProfile(pkg, info.user) - && !info.isActivity) { - if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: " - + info.launchIntent); - continue; - } - - // Generate a shortcut info to add into the model - installQueue.add(info.getItemInfo()); - } - prefs.edit().remove(APPS_PENDING_INSTALL).apply(); - if (!installQueue.isEmpty()) { - model.addAndBindAddedWorkspaceItems(installQueue); - } - return; + ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>(); + SharedPreferences prefs = Utilities.getPrefs(context); + Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); + if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); + if (strings == null) { + return; + } + + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); + for (String encoded : strings) { + PendingInstallShortcutInfo info = decode(encoded, context); + if (info == null) { + continue; + } + + String pkg = getIntentPackage(info.launchIntent); + if (!TextUtils.isEmpty(pkg) + && !launcherApps.isPackageEnabledForProfile(pkg, info.user) + && !info.isActivity) { + if (DBG) { + Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent); } + continue; } + + // Generate a shortcut info to add into the model + installQueue.add(info.getItemInfo()); + } + prefs.edit().remove(APPS_PENDING_INSTALL).apply(); + if (!installQueue.isEmpty()) { + model.addAndBindAddedWorkspaceItems(installQueue); } - }; + } public static void removeFromInstallQueue(Context context, HashSet<String> packageNames, UserHandle user) { @@ -289,7 +277,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) { // Queue the item up for adding if launcher has not loaded properly yet - Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget(); + MODEL_EXECUTOR.post(() -> addToQueue(context, info)); flushInstallQueue(context); } @@ -305,7 +293,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { if (sInstallQueueDisabledFlags != 0) { return; } - Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget(); + MODEL_EXECUTOR.post(() -> flushQueueInBackground(context)); } /** @@ -470,6 +458,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { .object() .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) .key(NAME_KEY).value(name) + .key(USER_HANDLE_KEY).value( + UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user)) .key(APP_SHORTCUT_TYPE_KEY).value(isActivity); if (icon != null) { byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon); @@ -493,7 +483,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { public Pair<ItemInfo, Object> getItemInfo() { if (isActivity) { - WorkspaceItemInfo si = createWorkspaceItemInfo(data, + WorkspaceItemInfo si = createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext)); si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; @@ -518,7 +508,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return Pair.create(widgetInfo, providerInfo); } else { WorkspaceItemInfo itemInfo = - createWorkspaceItemInfo(data, LauncherAppState.getInstance(mContext)); + createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext)); return Pair.create(itemInfo, null); } } @@ -623,7 +613,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { // Already an activity target return original; } - if (!Utilities.isLauncherAppTarget(original.launchIntent)) { + if (!PackageManagerHelper.isLauncherAppTarget(original.launchIntent)) { return original; } @@ -636,7 +626,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return new PendingInstallShortcutInfo(info, original.mContext); } - private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) { + private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user, + LauncherAppState app) { if (data == null) { Log.e(TAG, "Can't construct WorkspaceItemInfo with null data"); return null; @@ -653,10 +644,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } final WorkspaceItemInfo info = new WorkspaceItemInfo(); - - // Only support intents for current user for now. Intents sent from other - // users wouldn't get here without intent forwarding anyway. - info.user = Process.myUserHandle(); + info.user = user; BitmapInfo iconInfo = null; LauncherIcons li = LauncherIcons.obtain(app.getContext()); diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index bde87cb50..d66ba7317 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -18,6 +18,8 @@ package com.android.launcher3; import static com.android.launcher3.Utilities.getDevicePrefs; import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY; import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter; import android.annotation.TargetApi; @@ -39,11 +41,13 @@ import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; import android.util.Xml; -import android.view.Display; -import android.view.WindowManager; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.util.ConfigMonitor; +import com.android.launcher3.util.DefaultDisplay; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Themes; @@ -54,9 +58,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; +import java.util.Comparator; public class InvariantDeviceProfile { @@ -102,6 +104,8 @@ public class InvariantDeviceProfile { public int iconBitmapSize; public int fillResIconDpi; public float iconTextSize; + public float allAppsIconSize; + public float allAppsIconTextSize; private SparseArray<TypedValue> mExtraAttrs; @@ -110,6 +114,11 @@ public class InvariantDeviceProfile { */ public int numHotseatIcons; + /** + * Number of columns in the all apps list. + */ + public int numAllAppsColumns; + public int defaultLayoutId; int demoModeLayoutId; @@ -136,6 +145,9 @@ public class InvariantDeviceProfile { landscapeIconSize = p.landscapeIconSize; iconTextSize = p.iconTextSize; numHotseatIcons = p.numHotseatIcons; + numAllAppsColumns = p.numAllAppsColumns; + allAppsIconSize = p.allAppsIconSize; + allAppsIconTextSize = p.allAppsIconTextSize; defaultLayoutId = p.defaultLayoutId; demoModeLayoutId = p.demoModeLayoutId; mExtraAttrs = p.mExtraAttrs; @@ -144,7 +156,10 @@ public class InvariantDeviceProfile { @TargetApi(23) private InvariantDeviceProfile(Context context) { - initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)); + String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) + ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) + : null; + initGrid(context, gridName); mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess); mOverlayMonitor = new OverlayMonitor(context); @@ -172,64 +187,82 @@ public class InvariantDeviceProfile { } private String initGrid(Context context, String gridName) { - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - DisplayMetrics dm = new DisplayMetrics(); - display.getMetrics(dm); + DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo(); - Point smallestSize = new Point(); - Point largestSize = new Point(); - display.getCurrentSizeRange(smallestSize, largestSize); + Point smallestSize = new Point(displayInfo.smallestSize); + Point largestSize = new Point(displayInfo.largestSize); - ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName); // This guarantees that width < height - float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm); - float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm); + float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), + displayInfo.metrics); + float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), + displayInfo.metrics); + + Point realSize = new Point(displayInfo.realSize); + // The real size never changes. smallSide and largeSide will remain the + // same in any orientation. + int smallSide = Math.min(realSize.x, realSize.y); + int largeSide = Math.max(realSize.x, realSize.y); + + // We want a list of all options as well as the list of filtered options. This allows us + // to have a consistent UI for areas that the grid size change should not affect + // ie. All Apps should be consistent between grid sizes. + ArrayList<DisplayOption> allOptions = new ArrayList<>(); + ArrayList<DisplayOption> filteredOptions = new ArrayList<>(); + getPredefinedDeviceProfiles(context, gridName, filteredOptions, allOptions); + + if (allOptions.isEmpty() && filteredOptions.isEmpty()) { + throw new RuntimeException("No display option with canBeDefault=true"); + } + // Sort the profiles based on the closeness to the device size - Collections.sort(allOptions, (a, b) -> - Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps), - dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps))); + Comparator<DisplayOption> comparator = (a, b) -> Float.compare(dist(minWidthDps, + minHeightDps, a.minWidthDps, a.minHeightDps), + dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)); + + // Calculate the device profiles as if there is no grid override. + Collections.sort(allOptions, comparator); DisplayOption interpolatedDisplayOption = invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions); + initGridOption(context, allOptions, interpolatedDisplayOption, displayInfo.metrics); + + // Create IDP with no grid override values. + InvariantDeviceProfile originalIDP = new InvariantDeviceProfile(this); + originalIDP.landscapeProfile = new DeviceProfile(context, this, null, smallestSize, + largestSize, largeSide, smallSide, true /* isLandscape */, + false /* isMultiWindowMode */); + originalIDP.portraitProfile = new DeviceProfile(context, this, null, smallestSize, + largestSize, smallSide, largeSide, false /* isLandscape */, + false /* isMultiWindowMode */); + + if (filteredOptions.isEmpty()) { + filteredOptions = allOptions; + + landscapeProfile = originalIDP.landscapeProfile; + portraitProfile = originalIDP.portraitProfile; + } else { + Collections.sort(filteredOptions, comparator); + interpolatedDisplayOption = + invDistWeightedInterpolate(minWidthDps, minHeightDps, filteredOptions); + + initGridOption(context, filteredOptions, interpolatedDisplayOption, + displayInfo.metrics); + numAllAppsColumns = originalIDP.numAllAppsColumns; + + landscapeProfile = new DeviceProfile(context, this, originalIDP, smallestSize, + largestSize, largeSide, smallSide, true /* isLandscape */, + false /* isMultiWindowMode */); + portraitProfile = new DeviceProfile(context, this, originalIDP, smallestSize, + largestSize, smallSide, largeSide, false /* isLandscape */, + false /* isMultiWindowMode */); + } - GridOption closestProfile = allOptions.get(0).grid; - numRows = closestProfile.numRows; - numColumns = closestProfile.numColumns; - numHotseatIcons = closestProfile.numHotseatIcons; - defaultLayoutId = closestProfile.defaultLayoutId; - demoModeLayoutId = closestProfile.demoModeLayoutId; - numFolderRows = closestProfile.numFolderRows; - numFolderColumns = closestProfile.numFolderColumns; - mExtraAttrs = closestProfile.extraAttrs; - + GridOption closestProfile = filteredOptions.get(0).grid; if (!closestProfile.name.equals(gridName)) { Utilities.getPrefs(context).edit() .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply(); } - iconSize = interpolatedDisplayOption.iconSize; - iconShapePath = getIconShapePath(context); - landscapeIconSize = interpolatedDisplayOption.landscapeIconSize; - iconBitmapSize = ResourceUtils.pxFromDp(iconSize, dm); - iconTextSize = interpolatedDisplayOption.iconTextSize; - fillResIconDpi = getLauncherIconDensity(iconBitmapSize); - - // If the partner customization apk contains any grid overrides, apply them - // Supported overrides: numRows, numColumns, iconSize - applyPartnerDeviceProfileOverrides(context, dm); - - Point realSize = new Point(); - display.getRealSize(realSize); - // The real size never changes. smallSide and largeSide will remain the - // same in any orientation. - int smallSide = Math.min(realSize.x, realSize.y); - int largeSide = Math.max(realSize.x, realSize.y); - - landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize, - largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */); - portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize, - smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */); - // We need to ensure that there is enough extra space in the wallpaper // for the intended parallax effects if (context.getResources().getConfiguration().smallestScreenWidthDp >= 720) { @@ -246,6 +279,33 @@ public class InvariantDeviceProfile { return closestProfile.name; } + private void initGridOption(Context context, ArrayList<DisplayOption> options, + DisplayOption displayOption, DisplayMetrics metrics) { + GridOption closestProfile = options.get(0).grid; + numRows = closestProfile.numRows; + numColumns = closestProfile.numColumns; + numHotseatIcons = closestProfile.numHotseatIcons; + defaultLayoutId = closestProfile.defaultLayoutId; + demoModeLayoutId = closestProfile.demoModeLayoutId; + numFolderRows = closestProfile.numFolderRows; + numFolderColumns = closestProfile.numFolderColumns; + numAllAppsColumns = numColumns; + + mExtraAttrs = closestProfile.extraAttrs; + + iconSize = displayOption.iconSize; + iconShapePath = getIconShapePath(context); + landscapeIconSize = displayOption.landscapeIconSize; + iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics); + iconTextSize = displayOption.iconTextSize; + fillResIconDpi = getLauncherIconDensity(iconBitmapSize); + + // If the partner customization apk contains any grid overrides, apply them + // Supported overrides: numRows, numColumns, iconSize + applyPartnerDeviceProfileOverrides(context, metrics); + } + + @Nullable public TypedValue getAttrValue(int attr) { return mExtraAttrs == null ? null : mExtraAttrs.get(attr); @@ -280,7 +340,7 @@ public class InvariantDeviceProfile { public void setCurrentGrid(Context context, String gridName) { Context appContext = context.getApplicationContext(); Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply(); - new MainThreadExecutor().execute(() -> onConfigChanged(appContext)); + MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext)); } private void onConfigChanged(Context context) { @@ -288,9 +348,10 @@ public class InvariantDeviceProfile { InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this); // Re-init grid - // TODO(b/131867841): We pass in null here so that we can calculate the closest profile - // without the bias of the grid name. - initGrid(context, null); + String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) + ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) + : null; + initGrid(context, gridName); int changeFlags = 0; if (numRows != oldProfile.numRows || @@ -322,7 +383,13 @@ public class InvariantDeviceProfile { } } - static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context, String gridName) { + /** + * @param gridName The current grid name. + * @param filteredOptionsOut List filled with all the filtered options based on gridName. + * @param allOptionsOut List filled with all the options that can be the default option. + */ + static void getPredefinedDeviceProfiles(Context context, String gridName, + ArrayList<DisplayOption> filteredOptionsOut, ArrayList<DisplayOption> allOptionsOut) { ArrayList<DisplayOption> profiles = new ArrayList<>(); try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) { final int depth = parser.getDepth(); @@ -349,26 +416,19 @@ public class InvariantDeviceProfile { throw new RuntimeException(e); } - ArrayList<DisplayOption> filteredProfiles = new ArrayList<>(); if (!TextUtils.isEmpty(gridName)) { for (DisplayOption option : profiles) { if (gridName.equals(option.grid.name)) { - filteredProfiles.add(option); + filteredOptionsOut.add(option); } } } - if (filteredProfiles.isEmpty()) { - // No grid found, use the default options - for (DisplayOption option : profiles) { - if (option.canBeDefault) { - filteredProfiles.add(option); - } + + for (DisplayOption option : profiles) { + if (option.canBeDefault) { + allOptionsOut.add(option); } } - if (filteredProfiles.isEmpty()) { - throw new RuntimeException("No display option with canBeDefault=true"); - } - return filteredProfiles; } private int getLauncherIconDensity(int requiredSize) { @@ -514,6 +574,7 @@ public class InvariantDeviceProfile { R.styleable.GridDisplayOption_numFolderRows, numRows); numFolderColumns = a.getInt( R.styleable.GridDisplayOption_numFolderColumns, numColumns); + a.recycle(); extraAttrs = Themes.createValueMap(context, attrs, @@ -530,8 +591,8 @@ public class InvariantDeviceProfile { private final boolean canBeDefault; private float iconSize; - private float landscapeIconSize; private float iconTextSize; + private float landscapeIconSize; DisplayOption(GridOption grid, Context context, AttributeSet attrs) { this.grid = grid; @@ -549,6 +610,7 @@ public class InvariantDeviceProfile { landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize, iconSize); iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0); + a.recycle(); } diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index e29f92713..1550bb080 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -27,6 +27,8 @@ import com.android.launcher3.icons.BitmapInfo; */ public abstract class ItemInfoWithIcon extends ItemInfo { + public static final String TAG = "ItemInfoDebug"; + /** * A bitmap version of the application icon. */ @@ -126,4 +128,8 @@ public abstract class ItemInfoWithIcon extends ItemInfo { iconColor = info.color; } + /** + * @return a copy of this + */ + public abstract ItemInfoWithIcon clone(); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 257f0dfcf..491e5de85 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -16,10 +16,11 @@ package com.android.launcher3; -import static android.content.pm.ActivityInfo.CONFIG_LOCALE; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; +import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; +import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherState.ALL_APPS; @@ -77,6 +78,9 @@ import android.view.accessibility.AccessibilityEvent; import android.view.animation.OvershootInterpolator; import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsContainerView; @@ -91,9 +95,9 @@ import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; -import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.folder.FolderIconPreviewVerifier; +import com.android.launcher3.folder.FolderNameProvider; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.icons.IconCache; import com.android.launcher3.keyboard.CustomActionsPopup; @@ -103,14 +107,14 @@ import com.android.launcher3.logging.StatsLogUtils; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate; import com.android.launcher3.model.AppLaunchTracker; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; -import com.android.launcher3.shortcuts.DeepShortcutManager; +import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.states.InternalStateHandler; import com.android.launcher3.states.RotationHelper; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -145,7 +149,7 @@ import com.android.launcher3.widget.WidgetAddFlowHandler; import com.android.launcher3.widget.WidgetHostViewLoader; import com.android.launcher3.widget.WidgetListRowEntry; import com.android.launcher3.widget.WidgetsFullSheet; -import com.android.launcher3.widget.custom.CustomWidgetParser; +import com.android.launcher3.widget.custom.CustomWidgetManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -156,14 +160,11 @@ import java.util.HashSet; import java.util.List; import java.util.function.Predicate; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - /** * Default launcher application. */ public class Launcher extends BaseDraggingActivity implements LauncherExterns, - LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate, + Callbacks, LauncherProviderChangeListener, UserEventDelegate, InvariantDeviceProfile.OnIDPChangeListener { public static final String TAG = "Launcher"; static final boolean LOGD = false; @@ -220,9 +221,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private LauncherAppTransitionManager mAppTransitionManager; private Configuration mOldConfig; - @Thunk Workspace mWorkspace; + @Thunk + Workspace mWorkspace; private View mLauncherView; - @Thunk DragLayer mDragLayer; + @Thunk + DragLayer mDragLayer; private DragController mDragController; private AppWidgetManagerCompat mAppWidgetManager; @@ -230,21 +233,25 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private final int[] mTmpAddItemCellCoordinates = new int[2]; - @Thunk Hotseat mHotseat; + @Thunk + Hotseat mHotseat; private DropTargetBar mDropTargetBar; // Main container view for the all apps screen. - @Thunk AllAppsContainerView mAppsView; + @Thunk + AllAppsContainerView mAppsView; AllAppsTransitionController mAllAppsController; // Scrim view for the all apps and overview state. - @Thunk ScrimView mScrimView; + @Thunk + ScrimView mScrimView; // UI and state for the overview panel private View mOverviewPanel; - @Thunk boolean mWorkspaceLoading = true; + @Thunk + boolean mWorkspaceLoading = true; private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>(); @@ -326,8 +333,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, UiFactory.onCreate(this); mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); - - mAppWidgetHost = new LauncherAppWidgetHost(this); + mAppWidgetHost = new LauncherAppWidgetHost(this, + appWidgetId -> getWorkspace().removeWidget(appWidgetId)); mAppWidgetHost.startListening(); mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null); @@ -389,7 +396,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, RaceConditionTracker.onEvent(ON_CREATE_EVT, EXIT); mStateManager.addStateListener(new LauncherStateManager.StateListener() { @Override - public void onStateTransitionStart(LauncherState toState) {} + public void onStateTransitionStart(LauncherState toState) { + } @Override public void onStateTransitionComplete(LauncherState finalState) { @@ -419,10 +427,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, public void onConfigurationChanged(Configuration newConfig) { int diff = newConfig.diff(mOldConfig); - if ((diff & CONFIG_LOCALE) != 0) { - Folder.setLocaleDependentFields(getResources(), true /* force */); - } - if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) { onIdpChanged(mDeviceProfile.inv); } @@ -497,6 +501,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // Load configuration-specific DeviceProfile mDeviceProfile = idp.getDeviceProfile(this); if (isInMultiWindowMode()) { + // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return + // the app window size Display display = getWindowManager().getDefaultDisplay(); Point mwSize = new Point(); display.getSize(mwSize); @@ -548,6 +554,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return mStateManager; } + public FolderNameProvider getFolderNameProvider() { + return new FolderNameProvider(); + } + @Override public <T extends View> T findViewById(int id) { return mLauncherView.findViewById(id); @@ -604,10 +614,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (info.container >= 0) { View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container); if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) { - FolderIconPreviewVerifier verifier = - new FolderIconPreviewVerifier(getDeviceProfile().inv); - verifier.setFolderInfo((FolderInfo) folderIcon.getTag()); - if (verifier.isItemInPreview(info.rank)) { + if (new FolderGridOrganizer(getDeviceProfile().inv) + .setFolderInfo((FolderInfo) folderIcon.getTag()) + .isItemInPreview(info.rank)) { folderIcon.invalidate(); } } @@ -647,7 +656,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, .getLauncherAppWidgetInfo(widgetId); if (provider != null) { new WidgetAddFlowHandler(provider) - .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET); + .startConfigActivity(this, widgetInfo, + REQUEST_RECONFIGURE_APPWIDGET); } } break; @@ -835,7 +845,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } } - @Thunk void completeTwoStageWidgetDrop( + @Thunk + void completeTwoStageWidgetDrop( final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) { CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId); Runnable onCompleteRunnable = null; @@ -932,15 +943,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, int containerType = mStateManager.getState().containerType; if (containerType == ContainerType.WORKSPACE && mWorkspace != null) { getUserEventDispatcher().logActionCommand(command, - containerType, -1, mWorkspace.isOverlayShown() ? -1 : 0); + containerType, -1, mWorkspace.isOverlayShown() ? -1 : 0); } else { getUserEventDispatcher().logActionCommand(command, containerType, -1); } } - protected void onStateSet(LauncherState state) { - getAppWidgetHost().setResumed(state == LauncherState.NORMAL); + public void onStateSetStart(LauncherState state) { if (mDeferredResumePending) { handleDeferredResume(); } @@ -949,6 +959,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } } + public void onStateSetEnd(LauncherState state) { + getAppWidgetHost().setResumed(state == LauncherState.NORMAL); + getWorkspace().setClipChildren(!state.disablePageClipping); + finishAutoCancelActionMode(); + } + @Override protected void onResume() { RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER); @@ -1061,7 +1077,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mStateManager.goToState(state, false /* animated */); } - PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS); + PendingRequestArgs requestArgs = savedState.getParcelable( + RUNTIME_STATE_PENDING_REQUEST_ARGS); if (requestArgs != null) { setWaitingForResult(requestArgs); } @@ -1131,8 +1148,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, * Creates a view representing a shortcut inflated from the specified resource. * * @param parent The group the shortcut belongs to. - * @param info The data structure describing the shortcut. - * + * @param info The data structure describing the shortcut. * @return A View inflated from layoutResId. */ public View createShortcut(ViewGroup parent, WorkspaceItemInfo info) { @@ -1234,7 +1250,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, * * @param appWidgetId The app widget id */ - @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, + @Thunk + void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) { if (appWidgetInfo == null) { @@ -1352,7 +1369,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return mSharedPrefs; } - public int getOrientation() { return mOldConfig.orientation; } + public int getOrientation() { + return mOldConfig.orientation; + } @Override protected void onNewIntent(Intent intent) { @@ -1436,9 +1455,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, outState.remove(RUNTIME_STATE_WIDGET_PANEL); } - // We close any open folders and shortcut containers since they will not be re-opened, + // We close any open folders and shortcut containers that are not safe for rebind, // and we need to make sure this state is reflected. - AbstractFloatingView.closeAllOpenViews(this, false); + AbstractFloatingView.closeOpenViews(this, false, TYPE_ALL & ~TYPE_REBIND_SAFE); + finishAutoCancelActionMode(); if (mPendingRequestArgs != null) { outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs); @@ -1575,7 +1595,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, void addAppWidgetImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) { - if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) { + if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, + REQUEST_CREATE_APPWIDGET)) { // If the configuration flow was not started, add the widget Runnable onComplete = new Runnable() { @@ -1585,7 +1606,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); } }; - completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this)); + completeAddAppWidget(appWidgetId, info, boundWidget, + addFlowHandler.getProviderInfo(this)); mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false); } } @@ -1611,7 +1633,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, break; default: throw new IllegalStateException("Unknown item type: " + info.itemType); - } + } } /** @@ -1647,10 +1669,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } else { // In this case, we either need to start an activity to get permission to bind // the widget, or we need to start an activity to configure the widget, or both. - if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) { - appWidgetId = CustomWidgetParser.getWidgetIdForCustomProvider( - this, info.componentName); + if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) { + appWidgetId = CustomWidgetManager.INSTANCE.get(this).getWidgetIdForCustomProvider( + info.componentName); } else { appWidgetId = getAppWidgetHost().allocateAppWidgetId(); } @@ -1669,7 +1690,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX, int cellY) { final FolderInfo folderInfo = new FolderInfo(); - folderInfo.title = getText(R.string.folder_name); + folderInfo.title = ""; // Update the model getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY); @@ -1723,8 +1744,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return true; } - - @Override public boolean dispatchKeyEvent(KeyEvent event) { return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event); @@ -1788,7 +1807,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public int getCurrentState() { - if(mStateManager.getState() == LauncherState.ALL_APPS) { + if (mStateManager.getState() == LauncherState.ALL_APPS) { return StatsLogUtils.LAUNCHER_STATE_ALLAPPS; } else if (mStateManager.getState() == OVERVIEW) { return StatsLogUtils.LAUNCHER_STATE_OVERVIEW; @@ -1923,8 +1942,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // Floating panels (except the full widget sheet) are associated with individual icons. If // we are starting a fresh bind, close all such panels as all the icons are about // to go away. - AbstractFloatingView.closeOpenViews(this, true, - AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); + AbstractFloatingView.closeOpenViews(this, true, TYPE_ALL & ~TYPE_REBIND_SAFE); setWorkspaceLoading(true); @@ -2052,7 +2070,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, throw new RuntimeException("Invalid Item Type"); } - /* + /* * Remove colliding items. */ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { @@ -2124,6 +2142,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } private View inflateAppWidget(LauncherAppWidgetInfo item) { + if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) { + item.providerName = QsbContainerView.getSearchComponentName(this); + if (item.providerName == null) { + getModelWriter().deleteItemFromDatabase(item); + return null; + } + } + if (mIsSafeModeEnabled) { PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, mIconCache, true); @@ -2170,7 +2196,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, pendingInfo.spanY = item.spanY; pendingInfo.minSpanX = item.minSpanX; pendingInfo.minSpanY = item.minSpanY; - Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); + Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, + pendingInfo); boolean isDirectConfig = item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); @@ -2324,6 +2351,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // override the previous page so we don't log the page switch. mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */); + // Cache one page worth of icons + getViewCache().setCacheSize(R.layout.folder_application, + mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows); + getViewCache().setCacheSize(R.layout.folder_page, 2); + TraceHelper.endSection("finishBindingItems"); } @@ -2345,7 +2377,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, * * Implementation of the method from LauncherModel.Callbacks. */ - public void bindAllApplications(ArrayList<AppInfo> apps) { + public void bindAllApplications(AppInfo[] apps) { mAppsView.getAppsStore().setApps(apps); } @@ -2358,16 +2390,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy); } - /** - * A package was updated. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - @Override - public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps) { - mAppsView.getAppsStore().addOrUpdateApps(apps); - } - @Override public void bindPromiseAppProgressUpdated(PromiseAppInfo app) { mAppsView.getAppsStore().updatePromiseAppProgress(app); @@ -2415,11 +2437,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } @Override - public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) { - mAppsView.getAppsStore().removeApps(appInfos); - } - - @Override public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) { mPopupDataProvider.setAllWidgets(allWidgets); } @@ -2464,14 +2481,16 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } writer.println(prefix + "Misc:"); - writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading); - writer.print(" mPendingRequestArgs=" + mPendingRequestArgs); - writer.println(" mPendingActivityResult=" + mPendingActivityResult); - writer.println(" mRotationHelper: " + mRotationHelper); + dumpMisc(prefix + "\t", writer); + writer.println(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading); + writer.println(prefix + "\tmPendingRequestArgs=" + mPendingRequestArgs + + " mPendingActivityResult=" + mPendingActivityResult); + writer.println(prefix + "\tmRotationHelper: " + mRotationHelper); + writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening()); + // Extra logging for b/116853349 mDragLayer.dump(prefix, writer); mStateManager.dump(prefix, writer); - dumpMisc(writer); try { FileLog.flushAll(writer); @@ -2532,8 +2551,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (focusedView instanceof BubbleTextView && focusedView.getTag() instanceof ItemInfo && mAccessibilityDelegate.performAction(focusedView, - (ItemInfo) focusedView.getTag(), - LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { + (ItemInfo) focusedView.getTag(), + LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { PopupContainerWithArrow.getOpen(this).requestFocus(); return true; } @@ -2578,6 +2597,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return (Launcher) fromContext(context); } + @Override + public void returnToHomescreen() { + super.returnToHomescreen(); + getStateManager().goToState(LauncherState.NORMAL); + } + /** * Just a wrapper around the type cast to allow easier tracking of calls. */ diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index b4a2216c5..d70abc2a9 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -37,6 +37,7 @@ import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.SecureSettingsObserver; +import com.android.launcher3.widget.custom.CustomWidgetManager; public class LauncherAppState { @@ -149,6 +150,8 @@ public class LauncherAppState { LauncherModel setLauncher(Launcher launcher) { getLocalProvider(mContext).setLauncherProviderChangeListener(launcher); mModel.initialize(launcher); + CustomWidgetManager.INSTANCE.get(launcher) + .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts); return mModel; } diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java index 7f5ac5276..1215d4321 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/LauncherAppWidgetHost.java @@ -27,14 +27,15 @@ import android.content.Context; import android.content.Intent; import android.os.Handler; import android.util.SparseArray; -import android.view.LayoutInflater; import android.widget.Toast; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.widget.DeferredAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetHostView; +import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.ArrayList; +import java.util.function.IntConsumer; /** @@ -56,9 +57,17 @@ public class LauncherAppWidgetHost extends AppWidgetHost { private final Context mContext; private int mFlags = FLAG_RESUMED; + private IntConsumer mAppWidgetRemovedCallback = null; + public LauncherAppWidgetHost(Context context) { + this(context, null); + } + + public LauncherAppWidgetHost(Context context, + IntConsumer appWidgetRemovedCallback) { super(context, APPWIDGET_HOST_ID); mContext = context; + mAppWidgetRemovedCallback = appWidgetRemovedCallback; } @Override @@ -105,6 +114,10 @@ public class LauncherAppWidgetHost extends AppWidgetHost { super.stopListening(); } + public boolean isListening() { + return (mFlags & FLAG_LISTENING) != 0; + } + /** * Updates the resumed state of the host. * When a host is not resumed, it defers calls to startListening until host is resumed again. @@ -180,10 +193,8 @@ public class LauncherAppWidgetHost extends AppWidgetHost { LauncherAppWidgetProviderInfo appWidget) { if (appWidget.isCustomWidget()) { LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context); - LayoutInflater inflater = (LayoutInflater) - context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(appWidget.initialLayout, lahv); lahv.setAppWidget(0, appWidget); + CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv); return lahv; } else if ((mFlags & FLAG_LISTENING) == 0) { DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context); @@ -207,7 +218,7 @@ public class LauncherAppWidgetHost extends AppWidgetHost { } view.setAppWidget(appWidgetId, appWidget); view.switchToErrorView(); - return view; + return view; } } } @@ -225,6 +236,18 @@ public class LauncherAppWidgetHost extends AppWidgetHost { info.initSpans(mContext); } + /** + * Called on an appWidget is removed for a widgetId + * @param appWidgetId + * TODO: make this override when SDK is updated + */ + public void onAppWidgetRemoved(int appWidgetId) { + if (mAppWidgetRemovedCallback == null) { + return; + } + mAppWidgetRemovedCallback.accept(appWidgetId); + } + @Override public void deleteAppWidgetId(int appWidgetId) { super.deleteAppWidgetId(appWidgetId); diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index 051846c87..b82430184 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -29,6 +29,9 @@ import com.android.launcher3.util.ContentWriter; */ public class LauncherAppWidgetInfo extends ItemInfo { + public static final int OPTION_SEARCH_WIDGET = 1; + + public static final int RESTORE_COMPLETED = 0; /** @@ -97,6 +100,11 @@ public class LauncherAppWidgetInfo extends ItemInfo { public Intent bindOptions; /** + * Widget options + */ + public int options; + + /** * Nonnull for pending widgets. We use this to get the icon and title for the widget. */ public PackageItemInfo pendingItemInfo; @@ -137,6 +145,7 @@ public class LauncherAppWidgetInfo extends ItemInfo { writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId) .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString()) .put(LauncherSettings.Favorites.RESTORED, restoreStatus) + .put(LauncherSettings.Favorites.OPTIONS, options) .put(LauncherSettings.Favorites.INTENT, bindOptions); } @@ -164,4 +173,13 @@ public class LauncherAppWidgetInfo extends ItemInfo { public final boolean hasRestoreFlag(int flag) { return (restoreStatus & flag) == flag; } + + /** + * returns if widget options include an option or not + * @param option + * @return + */ + public final boolean hasOptionFlag(int option) { + return (options & option) != 0; + } } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index a0414894b..a012412c5 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -18,29 +18,32 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import androidx.annotation.Nullable; + import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.AddWorkspaceItemsTask; +import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BaseModelUpdateTask; import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.CacheDataUpdatedTask; import com.android.launcher3.model.LoaderResults; import com.android.launcher3.model.LoaderTask; @@ -50,29 +53,22 @@ import com.android.launcher3.model.PackageUpdatedTask; import com.android.launcher3.model.ShortcutsChangedTask; import com.android.launcher3.model.UserLockStateChangedTask; import com.android.launcher3.shortcuts.DeepShortcutManager; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; -import com.android.launcher3.util.ViewOnDrawExecutor; -import com.android.launcher3.widget.WidgetListRowEntry; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; import java.util.function.Supplier; -import androidx.annotation.Nullable; - /** * Maintains in-memory state of the Launcher. It is expected that there should be only one * LauncherModel object held in a static. Also provide APIs for updating the database state @@ -84,21 +80,12 @@ public class LauncherModel extends BroadcastReceiver static final String TAG = "Launcher.Model"; - private final MainThreadExecutor mUiExecutor = new MainThreadExecutor(); @Thunk final LauncherAppState mApp; @Thunk final Object mLock = new Object(); @Thunk LoaderTask mLoaderTask; @Thunk boolean mIsLoaderTaskRunning; - @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); - private static final Looper mWorkerLooper; - static { - sWorkerThread.start(); - mWorkerLooper = sWorkerThread.getLooper(); - } - @Thunk static final Handler sWorker = new Handler(mWorkerLooper); - // Indicates whether the current model data is valid or not. // We start off with everything not loaded. After that, we assume that // our monitoring of the package manager provides all updates and we never @@ -135,33 +122,6 @@ public class LauncherModel extends BroadcastReceiver } }; - public interface Callbacks { - public void rebindModel(); - - public int getCurrentWorkspaceScreen(); - public void clearPendingBinds(); - public void startBinding(); - public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons); - public void bindScreens(IntArray orderedScreenIds); - public void finishFirstPageBind(ViewOnDrawExecutor executor); - public void finishBindingItems(int pageBoundFirst); - public void bindAllApplications(ArrayList<AppInfo> apps); - public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps); - public void preAddApps(); - public void bindAppsAdded(IntArray newScreens, - ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated); - public void bindPromiseAppProgressUpdated(PromiseAppInfo app); - public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated); - public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); - public void bindRestoreItemsChange(HashSet<ItemInfo> updates); - public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); - public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos); - public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets); - public void onPageBoundSynchronously(int page); - public void executeOnNextDraw(ViewOnDrawExecutor executor); - public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap); - } - LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { mApp = app; mBgAllAppsList = new AllAppsList(iconCache, appFilter); @@ -174,11 +134,11 @@ public class LauncherModel extends BroadcastReceiver /** * Updates the icons and label of all pending icons for the provided package name. */ - public void updateSessionDisplayInfo(final String packageName) { + public void updateSessionDisplayInfo(final String packageName, final UserHandle user) { HashSet<String> packages = new HashSet<>(); packages.add(packageName); enqueueModelUpdateTask(new CacheDataUpdatedTask( - CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages)); + CacheDataUpdatedTask.OP_SESSION_UPDATE, user, packages)); } /** @@ -244,6 +204,7 @@ public class LauncherModel extends BroadcastReceiver public void onPackagesRemoved(UserHandle user, String... packages) { int op = PackageUpdatedTask.OP_REMOVE; + FileLog.d(TAG, "package removed received " + String.join("," + packages)); enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages)); } @@ -299,7 +260,6 @@ public class LauncherModel extends BroadcastReceiver @Override public void onReceive(Context context, Intent intent) { if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); - final String action = intent.getAction(); if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { // If we have changed locale we need to clear out the labels in all apps/workspace. @@ -375,7 +335,7 @@ 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. - mUiExecutor.execute(oldCallbacks::clearPendingBinds); + MAIN_EXECUTOR.execute(oldCallbacks::clearPendingBinds); // If there is already one running, tell it to stop. stopLoader(); @@ -419,7 +379,7 @@ public class LauncherModel extends BroadcastReceiver // Always post the loader task, instead of running directly (even on same thread) so // that we exit any nested synchronized blocks - sWorker.post(mLoaderTask); + MODEL_EXECUTOR.post(mLoaderTask); } } @@ -437,16 +397,7 @@ public class LauncherModel extends BroadcastReceiver @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.bindAppsAddedOrUpdated(arrayList); - } - }); - } + bindApplicationsIfNeeded(); } }); } @@ -495,8 +446,8 @@ public class LauncherModel extends BroadcastReceiver * use partial updates similar to {@link UserManagerCompat} */ public void refreshShortcutsIfRequired() { - sWorker.removeCallbacks(mShortcutPermissionCheckRunnable); - sWorker.post(mShortcutPermissionCheckRunnable); + MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable); + MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable); } /** @@ -523,14 +474,8 @@ public class LauncherModel extends BroadcastReceiver } public void enqueueModelUpdateTask(ModelUpdateTask task) { - task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor); - - if (sWorkerThread.getThreadId() == Process.myTid()) { - task.run(); - } else { - // If we are not on the worker thread, then post to the worker handler - sWorker.post(task); - } + task.init(mApp, this, sBgDataModel, mBgAllAppsList, MAIN_EXECUTOR); + MODEL_EXECUTOR.execute(task); } /** @@ -605,15 +550,4 @@ public class LauncherModel extends BroadcastReceiver public Callbacks getCallback() { return mCallbacks != null ? mCallbacks.get() : null; } - - /** - * @return the looper for the worker thread which can be used to start background tasks. - */ - public static Looper getWorkerLooper() { - return mWorkerLooper; - } - - public static void setWorkerPriority(final int priority) { - Process.setThreadPriority(sWorkerThread.getThreadId(), priority); - } } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 6ad5c3684..6081300ce 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -33,7 +33,6 @@ import android.content.Context; import android.content.Intent; import android.content.OperationApplicationException; import android.content.SharedPreferences; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ProviderInfo; import android.content.res.Resources; import android.database.Cursor; @@ -50,7 +49,6 @@ import android.os.Handler; import android.os.Message; import android.os.Process; import android.os.UserHandle; -import android.os.UserManager; import android.provider.BaseColumns; import android.provider.Settings; import android.text.TextUtils; @@ -70,6 +68,7 @@ import com.android.launcher3.util.IOUtils; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.NoLocaleSQLiteHelper; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; @@ -77,7 +76,6 @@ import org.xmlpull.v1.XmlPullParser; import java.io.File; import java.io.FileDescriptor; -import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringReader; @@ -873,7 +871,7 @@ public class LauncherProvider extends ContentProvider { continue; } - if (!Utilities.isLauncherAppTarget(intent)) { + if (!PackageManagerHelper.isLauncherAppTarget(intent)) { continue; } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index e248ba016..c50968098 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -59,6 +59,10 @@ public class LauncherSettings { public static final String ITEM_TYPE = "itemType"; /** + * The gesture is a package + */ + public static final int ITEM_TYPE_NON_ACTIONABLE = -1; + /** * The gesture is an application */ public static final int ITEM_TYPE_APPLICATION = 0; @@ -122,11 +126,13 @@ public class LauncherSettings { */ public static final int CONTAINER_DESKTOP = -100; public static final int CONTAINER_HOTSEAT = -101; + public static final int CONTAINER_PREDICTION = -102; static final String containerToString(int container) { switch (container) { case CONTAINER_DESKTOP: return "desktop"; case CONTAINER_HOTSEAT: return "hotseat"; + case CONTAINER_PREDICTION: return "prediction"; default: return String.valueOf(container); } } diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java index 63914b0eb..848e19fb5 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -136,7 +136,7 @@ public class LauncherStateManager { } public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + "LauncherState"); + writer.println(prefix + "LauncherState:"); writer.println(prefix + "\tmLastStableState:" + mLastStableState); writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState); writer.println(prefix + "\tmState:" + mState); @@ -227,11 +227,6 @@ public class LauncherStateManager { private void goToState(LauncherState state, boolean animated, long delay, final Runnable onCompleteRunnable) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "goToState: " + - state.getClass().getSimpleName() + - " @ " + Log.getStackTraceString(new Throwable())); - } animated &= Utilities.areAnimationsEnabled(mLauncher); if (mLauncher.isInState(state)) { if (mConfig.mCurrentAnimation == null) { @@ -412,13 +407,8 @@ public class LauncherStateManager { mState.onStateDisabled(mLauncher); } mState = state; - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.STABLE_STATE_MISMATCH, "onStateTransitionStart: " + - state.getClass().getSimpleName() + - " @ " + Log.getStackTraceString(new Throwable())); - } mState.onStateEnabled(mLauncher); - mLauncher.onStateSet(mState); + mLauncher.onStateSetStart(mState); if (state.disablePageClipping) { // Only disable clipping if needed, otherwise leave it as previous value. @@ -436,16 +426,10 @@ public class LauncherStateManager { if (state != mCurrentStableState) { mLastStableState = state.getHistoryForState(mCurrentStableState); mCurrentStableState = state; - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "onStateTransitionEnd: " + - state.getClass().getSimpleName() + - " @ " + Log.getStackTraceString(new Throwable())); - } } state.onStateTransitionEnd(mLauncher); - mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping); - mLauncher.finishAutoCancelActionMode(); + mLauncher.onStateSetEnd(state); if (state == NORMAL) { setRestState(null); diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 79d6ca793..ff2b40038 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -1296,6 +1296,10 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: { + Launcher launcher = Launcher.getLauncher(getContext()); + if (launcher != null) { + AbstractFloatingView.closeAllOpenViews(launcher); + } // Handle mouse (or ext. device) by shifting the page depending on the scroll final float vscroll; final float hscroll; @@ -1306,6 +1310,9 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); } + if (Math.abs(vscroll) > Math.abs(hscroll) && !isVerticalScrollable()) { + return true; + } if (hscroll != 0 || vscroll != 0) { boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0) : (hscroll > 0 || vscroll > 0); @@ -1322,6 +1329,10 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou return super.onGenericMotionEvent(event); } + protected boolean isVerticalScrollable() { + return true; + } + private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); @@ -1539,7 +1550,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou snapToPage(getNextPage() - 1); return true; } - return false; + return onOverscroll(-getMeasuredWidth()); } public boolean scrollRight() { @@ -1547,7 +1558,15 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou snapToPage(getNextPage() + 1); return true; } - return false; + return onOverscroll(getMeasuredWidth()); + } + + protected boolean onOverscroll(int amount) { + if (!mAllowOverScroll) return false; + onScrollInteractionBegin(); + overScroll(amount); + onScrollInteractionEnd(); + return true; } @Override @@ -1567,8 +1586,9 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); final boolean pagesFlipped = isPageOrderFlipped(); - info.setScrollable(getPageCount() > 1); - if (getCurrentPage() < getPageCount() - 1) { + int offset = (mAllowOverScroll ? 0 : 1); + info.setScrollable(getPageCount() > offset); + if (getCurrentPage() < getPageCount() - offset) { info.addAction(pagesFlipped ? AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); @@ -1576,7 +1596,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT); } - if (getCurrentPage() > 0) { + if (getCurrentPage() >= offset) { info.addAction(pagesFlipped ? AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); @@ -1584,7 +1604,6 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT); } - // Accessibility-wise, PagedView doesn't support long click, so disabling it. // Besides disabling the accessibility long-click, this also prevents this view from getting // accessibility focus. @@ -1603,7 +1622,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - event.setScrollable(getPageCount() > 1); + event.setScrollable(mAllowOverScroll || getPageCount() > 1); } @Override diff --git a/src/com/android/launcher3/Partner.java b/src/com/android/launcher3/Partner.java index 380078b26..af5402ae2 100644 --- a/src/com/android/launcher3/Partner.java +++ b/src/com/android/launcher3/Partner.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.util.PackageManagerHelper.findSystemApk; + import android.content.pm.PackageManager; import android.content.res.Resources; import android.util.DisplayMetrics; @@ -59,7 +61,7 @@ public class Partner { */ public static synchronized Partner get(PackageManager pm) { if (!sSearched) { - Pair<String, Resources> apkInfo = Utilities.findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm); + Pair<String, Resources> apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm); if (apkInfo != null) { sPartner = new Partner(apkInfo.first, apkInfo.second); } diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java index 55cb6f214..c8c590d9d 100644 --- a/src/com/android/launcher3/SecondaryDropTarget.java +++ b/src/com/android/launcher3/SecondaryDropTarget.java @@ -2,6 +2,7 @@ package com.android.launcher3; import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE; + import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK; import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; @@ -29,6 +30,7 @@ import android.widget.Toast; import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; @@ -240,6 +242,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName())) .putExtra(Intent.EXTRA_USER, info.user); mLauncher.startActivity(i); + FileLog.d(TAG, "start uninstall activity " + cn.getPackageName()); return cn; } catch (URISyntaxException e) { Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info); diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index b4078ee0e..a87c44658 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -22,7 +22,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; @@ -39,6 +38,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.util.Executors; import com.android.launcher3.compat.PackageInstallerCompat; import java.util.List; @@ -71,8 +71,13 @@ public class SessionCommitReceiver extends BroadcastReceiver { SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION); UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); - PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context); + if (!PackageInstaller.ACTION_SESSION_COMMITTED.equals(intent.getAction()) + || info == null || user == null) { + // Invalid intent. + return; + } + PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context); if (TextUtils.isEmpty(info.getAppPackageName()) || info.getInstallReason() != PackageManager.INSTALL_REASON_USER || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) { @@ -133,7 +138,7 @@ public class SessionCommitReceiver extends BroadcastReceiver { // grid. prefs.edit().putBoolean(ADD_ICON_PREFERENCE_KEY, true).apply(); } else if (!prefs.contains(ADD_ICON_PREFERENCE_INITIALIZED_KEY)) { - new PrefInitTask(context).executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); + new PrefInitTask(context).executeOnExecutor(Executors.THREAD_POOL_EXECUTOR); } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index ba122f944..5d0effa5f 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -24,15 +24,9 @@ import android.app.ActivityManager; import android.app.Person; import android.app.WallpaperManager; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.res.Resources; @@ -47,7 +41,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; import android.os.Message; @@ -60,7 +53,6 @@ import android.text.TextUtils; import android.text.style.TtsSpan; import android.util.DisplayMetrics; import android.util.Log; -import android.util.Pair; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; @@ -69,7 +61,6 @@ import android.view.animation.Interpolator; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.graphics.TintedDrawableSpan; @@ -81,17 +72,10 @@ import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.views.Transposable; import com.android.launcher3.widget.PendingAddShortcutInfo; -import java.io.Closeable; -import java.io.IOException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Locale; -import java.util.StringTokenizer; -import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -124,8 +108,6 @@ public final class Utilities { public static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - public static final int SINGLE_FRAME_MS = 16; - /** * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}. */ @@ -148,18 +130,6 @@ public final class Utilities { public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET"; public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR"; - // These values are same as that in {@link AsyncTask}. - private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); - private static final int CORE_POOL_SIZE = CPU_COUNT + 1; - private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; - private static final int KEEP_ALIVE = 1; - /** - * An {@link Executor} to be used with async task with no limit on the queue size. - */ - public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( - CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, - TimeUnit.SECONDS, new LinkedBlockingQueue<>()); - public static boolean IS_RUNNING_IN_TEST_HARNESS = ActivityManager.isRunningInTestHarness(); @@ -247,7 +217,6 @@ public final class Utilities { return scale; } - /** * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}. */ @@ -383,53 +352,6 @@ public final class Utilities { return min + (value * (max - min)); } - public static boolean isSystemApp(Context context, Intent intent) { - PackageManager pm = context.getPackageManager(); - ComponentName cn = intent.getComponent(); - String packageName = null; - if (cn == null) { - ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); - if ((info != null) && (info.activityInfo != null)) { - packageName = info.activityInfo.packageName; - } - } else { - packageName = cn.getPackageName(); - } - if (packageName != null) { - try { - PackageInfo info = pm.getPackageInfo(packageName, 0); - return (info != null) && (info.applicationInfo != null) && - ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); - } catch (NameNotFoundException e) { - return false; - } - } else { - return false; - } - } - - /* - * Finds a system apk which had a broadcast receiver listening to a particular action. - * @param action intent action used to find the apk - * @return a pair of apk package name and the resources. - */ - static Pair<String, Resources> findSystemApk(String action, PackageManager pm) { - final Intent intent = new Intent(action); - for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) { - if (info.activityInfo != null && - (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - final String packageName = info.activityInfo.packageName; - try { - final Resources res = pm.getResourcesForApplication(packageName); - return Pair.create(packageName, res); - } catch (NameNotFoundException e) { - Log.w(TAG, "Failed to find resources for " + packageName); - } - } - } - return null; - } - /** * Trims the string, removing all whitespace at the beginning and end of the string. * Non-breaking whitespaces are also removed. @@ -454,51 +376,10 @@ public final class Utilities { return (int) Math.ceil(fm.bottom - fm.top); } - /** - * Convenience println with multiple args. - */ - public static void println(String key, Object... args) { - StringBuilder b = new StringBuilder(); - b.append(key); - b.append(": "); - boolean isFirstArgument = true; - for (Object arg : args) { - if (isFirstArgument) { - isFirstArgument = false; - } else { - b.append(", "); - } - b.append(arg); - } - System.out.println(b.toString()); - } - public static boolean isRtl(Resources res) { return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; } - /** - * Returns true if the intent is a valid launch intent for a launcher activity of an app. - * This is used to identify shortcuts which are different from the ones exposed by the - * applications' manifest file. - * - * @param launchIntent The intent that will be launched when the shortcut is clicked. - */ - public static boolean isLauncherAppTarget(Intent launchIntent) { - if (launchIntent != null - && Intent.ACTION_MAIN.equals(launchIntent.getAction()) - && launchIntent.getComponent() != null - && launchIntent.getCategories() != null - && launchIntent.getCategories().size() == 1 - && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER) - && TextUtils.isEmpty(launchIntent.getDataString())) { - // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE. - Bundle extras = launchIntent.getExtras(); - return extras == null || extras.keySet().isEmpty(); - } - return false; - } - public static float dpiFromPx(int size, DisplayMetrics metrics){ float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT; return (size / densityRatio); @@ -601,18 +482,6 @@ public final class Utilities { return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed(); } - public static void closeSilently(Closeable c) { - if (c != null) { - try { - c.close(); - } catch (IOException e) { - if (FeatureFlags.IS_DOGFOOD_BUILD) { - Log.d(TAG, "Error closing", e); - } - } - } - } - public static boolean isBinderSizeError(Exception e) { return e.getCause() instanceof TransactionTooLargeException || e.getCause() instanceof DeadObjectException; @@ -715,7 +584,7 @@ public final class Utilities { LauncherIcons li = LauncherIcons.obtain(appState.getContext()); Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap; li.recycle(); - float badgeSize = launcher.getResources().getDimension(R.dimen.profile_badge_size); + float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize); float insetFraction = (iconSize - badgeSize) / iconSize; return new InsetDrawable(new FastBitmapDrawable(badge), insetFraction, insetFraction, 0, 0); @@ -727,25 +596,6 @@ public final class Utilities { } } - public static int[] getIntArrayFromString(String tokenized) { - StringTokenizer tokenizer = new StringTokenizer(tokenized, ","); - int[] array = new int[tokenizer.countTokens()]; - int count = 0; - while (tokenizer.hasMoreTokens()) { - array[count] = Integer.parseInt(tokenizer.nextToken().trim()); - count++; - } - return array; - } - - public static String getStringFromIntArray(int[] array) { - StringBuilder str = new StringBuilder(); - for (int value : array) { - str.append(value).append(","); - } - return str.toString(); - } - public static float squaredHypot(float x, float y) { return x * x + y * y; } diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index 6d1bc1a9c..c6381b058 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -1,5 +1,8 @@ package com.android.launcher3; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; @@ -23,21 +26,23 @@ import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.CancellationSignal; -import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.Log; import android.util.LongSparseArray; +import androidx.annotation.Nullable; + import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.icons.GraphicsUtils; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.ShadowGenerator; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.SQLiteCacheHelper; @@ -50,11 +55,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.WeakHashMap; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import androidx.annotation.Nullable; - public class WidgetPreviewLoader { private static final String TAG = "WidgetPreviewLoader"; @@ -68,23 +70,18 @@ public class WidgetPreviewLoader { * Note: synchronized block used for this variable is expensive and the block should always * be posted to a background thread. */ - @Thunk final Set<Bitmap> mUnusedBitmaps = - Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>()); + @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>()); private final Context mContext; private final IconCache mIconCache; private final UserManagerCompat mUserManager; private final CacheDb mDb; - private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); - @Thunk final Handler mWorkerHandler; - public WidgetPreviewLoader(Context context, IconCache iconCache) { mContext = context; mIconCache = iconCache; mUserManager = UserManagerCompat.getInstance(context); mDb = new CacheDb(context); - mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); } /** @@ -99,7 +96,7 @@ public class WidgetPreviewLoader { WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size); PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller); - task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); + task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR); CancellationSignal signal = new CancellationSignal(); signal.setOnCancelListener(task); @@ -453,7 +450,7 @@ public class WidgetPreviewLoader { private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info, int maxWidth, int maxHeight, Bitmap preview) { - int iconSize = launcher.getDeviceProfile().iconSizePx; + int iconSize = launcher.getDeviceProfile().allAppsIconSizePx; int padding = launcher.getResources() .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding); @@ -494,12 +491,7 @@ public class WidgetPreviewLoader { private Drawable mutateOnMainThread(final Drawable drawable) { try { - return mMainThreadExecutor.submit(new Callable<Drawable>() { - @Override - public Drawable call() throws Exception { - return drawable.mutate(); - } - }).get(); + return MAIN_EXECUTOR.submit(drawable::mutate).get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); @@ -607,7 +599,7 @@ public class WidgetPreviewLoader { // Write the generated preview to the DB in the worker thread if (mVersions != null) { - mWorkerHandler.post(new Runnable() { + MODEL_EXECUTOR.post(new Runnable() { @Override public void run() { if (!isCancelled()) { @@ -637,7 +629,7 @@ public class WidgetPreviewLoader { // recycled set immediately. Otherwise, it will be recycled after the preview is written // to disk. if (preview != null) { - mWorkerHandler.post(new Runnable() { + MODEL_EXECUTOR.post(new Runnable() { @Override public void run() { synchronized (mUnusedBitmaps) { @@ -658,7 +650,7 @@ public class WidgetPreviewLoader { // in the tasks's onCancelled() call, and if cancelled while the task is writing to // disk, it will be cancelled in the task's onPostExecute() call. if (mBitmapToRecycle != null) { - mWorkerHandler.post(new Runnable() { + MODEL_EXECUTOR.post(new Runnable() { @Override public void run() { synchronized (mUnusedBitmaps) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 6612662ea..9eeb2866b 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -67,9 +67,9 @@ import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; @@ -90,8 +90,8 @@ import com.android.launcher3.touch.WorkspaceTouchListener; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; @@ -133,9 +133,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator> private static final int DEFAULT_PAGE = 0; - public static final boolean MAP_NO_RECURSE = false; - public static final boolean MAP_RECURSE = true; - private LayoutTransition mLayoutTransition; @Thunk final WallpaperManager mWallpaperManager; @@ -1038,6 +1035,13 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } @Override + protected boolean onOverscroll(int amount) { + // Enforce overscroll on -1 direction + if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false; + return super.onOverscroll(amount); + } + + @Override protected boolean shouldFlingForVelocity(int velocityX) { // When the overlay is moving, the fling or settle transition is controlled by the overlay. return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 && @@ -1161,7 +1165,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } protected void setWallpaperDimension() { - Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() { + Executors.THREAD_POOL_EXECUTOR.execute(new Runnable() { @Override public void run() { final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize; @@ -1271,6 +1275,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator> return !mLauncher.isInState(NORMAL); } + private boolean workspaceInScrollableState() { + return mLauncher.isInState(SPRING_LOADED) || !workspaceInModalState(); + } + /** Returns whether a drag should be allowed to be started from the current workspace state. */ public boolean workspaceIconsCanBeDragged() { return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged; @@ -1464,6 +1472,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator> public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_CONTEXT_MENU, "beginDragShared"); + } float iconScale = 1f; if (child instanceof BubbleTextView) { Drawable icon = ((BubbleTextView) child).getIcon(); @@ -1489,7 +1500,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> Rect dragRect = null; if (child instanceof BubbleTextView) { dragRect = new Rect(); - ((BubbleTextView) child).getIconBounds(dragRect); + BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx); dragLayerY += dragRect.top; // Note: The dragRect is used to calculate drag layer offsets, but the // dragVisualizeOffset in addition to the dragRect (the size) to position the outline. @@ -2532,22 +2543,22 @@ public class Workspace extends PagedView<WorkspacePageIndicator> View view; switch (info.itemType) { - case ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: - if (info.container == NO_ID && info instanceof AppInfo) { - // Came from all apps -- make a copy - info = ((AppInfo) info).makeWorkspaceItem(); - d.dragInfo = info; - } - view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info); - break; - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, - (FolderInfo) info); - break; - default: - throw new IllegalStateException("Unknown item type: " + info.itemType); + case ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: + if (info instanceof AppInfo) { + // Came from all apps -- make a copy + info = ((AppInfo) info).makeWorkspaceItem(); + d.dragInfo = info; + } + view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info); + break; + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, + (FolderInfo) info); + break; + default: + throw new IllegalStateException("Unknown item type: " + info.itemType); } // First we find the cell nearest to point at which the item is @@ -2805,10 +2816,27 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } /** + * Removed widget from workspace by appWidgetId + * @param appWidgetId + */ + public void removeWidget(int appWidgetId) { + mapOverItems((info, view) -> { + if (info instanceof LauncherAppWidgetInfo) { + LauncherAppWidgetInfo appWidgetInfo = (LauncherAppWidgetInfo) info; + if (appWidgetInfo.appWidgetId == appWidgetId) { + mLauncher.removeItem(view, appWidgetInfo, true); + return true; + } + } + return false; + }); + } + + /** * Removes all folder listeners */ public void removeFolderListeners() { - mapOverItems(false, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View view) { if (view instanceof FolderIcon) { @@ -2855,7 +2883,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> @Override public boolean scrollLeft() { boolean result = false; - if (!workspaceInModalState() && !mIsSwitchingState) { + if (!mIsSwitchingState && workspaceInScrollableState()) { result = super.scrollLeft(); } Folder openFolder = Folder.getOpen(mLauncher); @@ -2868,7 +2896,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> @Override public boolean scrollRight() { boolean result = false; - if (!workspaceInModalState() && !mIsSwitchingState) { + if (!mIsSwitchingState && workspaceInScrollableState()) { result = super.scrollRight(); } Folder openFolder = Folder.getOpen(mLauncher); @@ -2960,7 +2988,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> public View getFirstMatch(final ItemOperator operator) { final View[] value = new View[1]; - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v) { if (operator.evaluate(info, v)) { @@ -2983,7 +3011,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> final View[] matches = new View[operators.length]; // For efficiency, the outer loop should be CellLayout. for (CellLayout cellLayout : cellLayouts) { - mapOverCellLayout(MAP_NO_RECURSE, cellLayout, (info, v) -> { + mapOverCellLayout(cellLayout, (info, v) -> { for (int i = 0; i < operators.length; ++i) { if (matches[i] == null && operators[i].evaluate(info, v)) { matches[i] = v; @@ -3008,7 +3036,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } void clearDropTargets() { - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v) { if (v instanceof DropTarget) { @@ -3053,10 +3081,12 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } else if (itemToRemove.container >= 0) { // The item may belong to a folder. View parent = idToViewMap.get(itemToRemove.container); - if (parent != null) { + if (parent instanceof FolderIcon) { FolderInfo folderInfo = (FolderInfo) parent.getTag(); - folderInfo.prepareAutoUpdate(); folderInfo.remove((WorkspaceItemInfo) itemToRemove, false); + if (((FolderIcon) parent).getFolder().isOpen()) { + ((FolderIcon) parent).getFolder().close(false /* animate */); + } } } } @@ -3080,18 +3110,17 @@ public class Workspace extends PagedView<WorkspacePageIndicator> /** * Map the operator over the shortcuts and widgets, return the first-non-null value. * - * @param recurse true: iterate over folder children. false: op get the folders themselves. * @param op the operator to map over the shortcuts */ - public void mapOverItems(boolean recurse, ItemOperator op) { + public void mapOverItems(ItemOperator op) { for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { - if (mapOverCellLayout(recurse, layout, op)) { + if (mapOverCellLayout(layout, op)) { return; } } } - private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) { + private boolean mapOverCellLayout(CellLayout layout, ItemOperator op) { // TODO(b/128460496) Potential race condition where layout is not yet loaded if (layout == null) { return false; @@ -3101,103 +3130,68 @@ public class Workspace extends PagedView<WorkspacePageIndicator> final int itemCount = container.getChildCount(); for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { View item = container.getChildAt(itemIdx); - ItemInfo info = (ItemInfo) item.getTag(); - if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) { - FolderIcon folder = (FolderIcon) item; - ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder(); - // map over all the children in the folder - final int childCount = folderChildren.size(); - for (int childIdx = 0; childIdx < childCount; childIdx++) { - View child = folderChildren.get(childIdx); - info = (ItemInfo) child.getTag(); - if (op.evaluate(info, child)) { - return true; - } - } - } else { - if (op.evaluate(info, item)) { - return true; - } + if (op.evaluate((ItemInfo) item.getTag(), item)) { + return true; } } return false; } void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) { - int total = shortcuts.size(); - final HashSet<WorkspaceItemInfo> updates = new HashSet<>(total); - final IntSet folderIds = new IntSet(); + final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts); + ItemOperator op = (info, v) -> { + if (v instanceof BubbleTextView && updates.contains(info)) { + WorkspaceItemInfo si = (WorkspaceItemInfo) info; + BubbleTextView shortcut = (BubbleTextView) v; + Drawable oldIcon = shortcut.getIcon(); + boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) + && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); + shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); + } else if (info instanceof FolderInfo && v instanceof FolderIcon) { + ((FolderIcon) v).updatePreviewItems(updates::contains); + } + + // Iterate all items + return false; + }; - for (int i = 0; i < total; i++) { - WorkspaceItemInfo s = shortcuts.get(i); - updates.add(s); - folderIds.add(s.container); + mapOverItems(op); + Folder openFolder = Folder.getOpen(mLauncher); + if (openFolder != null) { + openFolder.iterateOverItems(op); } - - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView && - updates.contains(info)) { - WorkspaceItemInfo si = (WorkspaceItemInfo) info; - BubbleTextView shortcut = (BubbleTextView) v; - Drawable oldIcon = shortcut.getIcon(); - boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) - && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); - shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); - } - // process all the shortcuts - return false; - } - }); - - // Update folder icons - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof FolderInfo && folderIds.contains(info.id)) { - ((FolderInfo) info).itemsChanged(false); - } - // process all the shortcuts - return false; - } - }); } public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) { final PackageUserKey packageUserKey = new PackageUserKey(null, null); - final IntSet folderIds = new IntSet(); - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) { - if (!packageUserKey.updateFromItemInfo(info) - || updatedDots.test(packageUserKey)) { - ((BubbleTextView) v).applyDotState(info, true /* animate */); - folderIds.add(info.container); - } - } - // process all the shortcuts - return false; - } - }); + Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info) + || updatedDots.test(packageUserKey); - // Update folder icons - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof FolderInfo && folderIds.contains(info.id) - && v instanceof FolderIcon) { + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) { + if (matcher.test(info)) { + ((BubbleTextView) v).applyDotState(info, true /* animate */); + } + } else if (info instanceof FolderInfo && v instanceof FolderIcon) { + FolderInfo fi = (FolderInfo) info; + if (fi.contents.stream().anyMatch(matcher)) { FolderDotInfo folderDotInfo = new FolderDotInfo(); - for (WorkspaceItemInfo si : ((FolderInfo) info).contents) { + for (WorkspaceItemInfo si : fi.contents) { folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si)); } ((FolderIcon) v).setDotInfo(folderDotInfo); } - // process all the shortcuts - return false; } - }); + + // process all the shortcuts + return false; + }; + + mapOverItems(op); + Folder folder = Folder.getOpen(mLauncher); + if (folder != null) { + folder.iterateOverItems(op); + } } public void removeAbandonedPromise(String packageName, UserHandle user) { @@ -3209,21 +3203,25 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } public void updateRestoreItems(final HashSet<ItemInfo> updates) { - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView - && updates.contains(info)) { - ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */); - } else if (v instanceof PendingAppWidgetHostView - && info instanceof LauncherAppWidgetInfo - && updates.contains(info)) { - ((PendingAppWidgetHostView) v).applyState(); - } - // process all the shortcuts - return false; - } - }); + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView + && updates.contains(info)) { + ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */); + } else if (v instanceof PendingAppWidgetHostView + && info instanceof LauncherAppWidgetInfo + && updates.contains(info)) { + ((PendingAppWidgetHostView) v).applyState(); + } else if (v instanceof FolderIcon && info instanceof FolderInfo) { + ((FolderIcon) v).updatePreviewItems(updates::contains); + } + // process all the shortcuts + return false; + }; + mapOverItems(op); + Folder folder = Folder.getOpen(mLauncher); + if (folder != null) { + folder.iterateOverItems(op); + } } public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) { @@ -3247,7 +3245,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } else { // widgetRefresh will automatically run when the packages are updated. // For now just update the progress bars - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View view) { if (view instanceof PendingAppWidgetHostView @@ -3371,7 +3369,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> mRefreshPending = false; ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size()); - mapOverItems(MAP_NO_RECURSE, (info, view) -> { + mapOverItems((info, view) -> { if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { views.add((PendingAppWidgetHostView) view); } diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java index 050a8bef7..23795c5c0 100644 --- a/src/com/android/launcher3/WorkspaceItemInfo.java +++ b/src/com/android/launcher3/WorkspaceItemInfo.java @@ -212,7 +212,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { public ComponentName getTargetComponent() { ComponentName cn = super.getTargetComponent(); if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT - || hasStatusFlag(FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON))) { + || hasStatusFlag(FLAG_SUPPORTS_WEB_UI|FLAG_AUTOINSTALL_ICON|FLAG_RESTORED_ICON))) { // Legacy shortcuts and promise icons with web UI may not have a componentName but just // a packageName. In that case create a dummy componentName instead of adding additional // check everywhere. @@ -221,4 +221,9 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { } return cn; } + + @Override + public ItemInfoWithIcon clone() { + return new WorkspaceItemInfo(this); + } } diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 40c6b5f1b..7a7e1fee6 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -38,7 +38,6 @@ import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.graphics.WorkspaceAndHotseatScrim; /** @@ -96,14 +95,13 @@ public class WorkspaceStateTransitionAnimation { propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator); if (!hotseat.getRotationMode().isTransposed) { - // Set the hotseat's pivot point to match the workspace's, so that it scales together. - DragLayer dragLayer = mLauncher.getDragLayer(); - float[] workspacePivot = - new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() }; - dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot); - dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot); - hotseat.setPivotX(workspacePivot[0]); - hotseat.setPivotY(workspacePivot[1]); + // Set the hotseat's pivot point to match the workspace's, so that it scales + // together. Since both hotseat and workspace can move, transform the point + // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and + // related methods. + hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop()); + hotseat.setPivotX(mWorkspace.getPivotX() + + mWorkspace.getLeft() - hotseat.getLeft()); } float hotseatScale = hotseatScaleAndTranslation.scale; Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE, diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 293b86722..37ee24850 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -312,6 +312,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo + grid.cellLayoutPaddingLeftRightPx; for (int i = 0; i < mAH.length; i++) { + mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns); mAH[i].padding.bottom = insets.bottom; mAH[i].padding.left = mAH[i].padding.right = leftRightPadding; mAH[i].applyPadding(); diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 3cfa0b1ec..bb212686e 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -180,7 +180,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. private final GridLayoutManager mGridLayoutMgr; private final GridSpanSizer mGridSizer; - private final int mAppsPerRow; + private int mAppsPerRow; private BindViewCallback mBindViewCallback; private OnFocusChangeListener mIconFocusListener; @@ -200,7 +200,11 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. mGridLayoutMgr.setSpanSizeLookup(mGridSizer); mLayoutInflater = LayoutInflater.from(launcher); - mAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns; + setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns); + } + + public void setAppsPerRow(int appsPerRow) { + mAppsPerRow = appsPerRow; mGridLayoutMgr.setSpanCount(mAppsPerRow); } diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java index 69068c6ee..5b7394048 100644 --- a/src/com/android/launcher3/allapps/AllAppsPagedView.java +++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java @@ -81,4 +81,9 @@ public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> { public boolean hasOverlappingRendering() { return false; } + + @Override + protected boolean isVerticalScrollable() { + return false; + } } diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index ca8dbebfc..c4b2f68c9 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -15,6 +15,9 @@ */ package com.android.launcher3.allapps; +import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR; +import static com.android.launcher3.AppInfo.EMPTY_ARRAY; + import android.view.View; import android.view.ViewGroup; @@ -26,8 +29,7 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; +import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; @@ -43,27 +45,33 @@ public class AllAppsStore { public static final int DEFER_UPDATES_TEST = 1 << 1; private PackageUserKey mTempKey = new PackageUserKey(null, null); - private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>(); + private AppInfo mTempInfo = new AppInfo(); + + private AppInfo[] mApps = EMPTY_ARRAY; + private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>(); private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>(); private int mDeferUpdatesFlags = 0; private boolean mUpdatePending = false; - public Collection<AppInfo> getApps() { - return mComponentToAppMap.values(); + public AppInfo[] getApps() { + return mApps; } /** * Sets the current set of apps. */ - public void setApps(List<AppInfo> apps) { - mComponentToAppMap.clear(); - addOrUpdateApps(apps); + public void setApps(AppInfo[] apps) { + mApps = apps; + notifyUpdate(); } public AppInfo getApp(ComponentKey key) { - return mComponentToAppMap.get(key); + mTempInfo.componentName = key.componentName; + mTempInfo.user = key.user; + int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR); + return index < 0 ? null : mApps[index]; } public void enableDeferUpdates(int flag) { @@ -86,27 +94,6 @@ public class AllAppsStore { return mDeferUpdatesFlags; } - /** - * Adds or updates existing apps in the list - */ - public void addOrUpdateApps(List<AppInfo> apps) { - for (AppInfo app : apps) { - mComponentToAppMap.put(app.toComponentKey(), app); - } - notifyUpdate(); - } - - /** - * Removes some apps from the list. - */ - public void removeApps(List<AppInfo> apps) { - for (AppInfo app : apps) { - mComponentToAppMap.remove(app.toComponentKey()); - } - notifyUpdate(); - } - - private void notifyUpdate() { if (mDeferUpdatesFlags != 0) { mUpdatePending = true; diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 3836c9fdb..08ce9c24f 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -2,8 +2,6 @@ package com.android.launcher3.allapps; import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; -import static com.android.launcher3.LauncherState.BACKGROUND_APP; -import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR; import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE; @@ -134,15 +132,6 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil } else { mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0); } - - if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { - // Translate hotseat with the shelf until reaching overview. - float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher); - if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) { - float hotseatShift = (progress - overviewProgress) * mShiftRange; - mLauncher.getHotseat().setTranslationY(hotseatShift); - } - } } public float getProgress() { diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 1369441fe..0c4be6287 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -21,16 +21,13 @@ import android.content.pm.PackageManager; import com.android.launcher3.AppInfo; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; -import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.shortcuts.DeepShortcutManager; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LabelComparator; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -145,9 +142,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { // The of ordered component names as a result of a search query private ArrayList<ComponentKey> mSearchResults; - private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>(); private AllAppsGridAdapter mAdapter; - private AlphabeticIndexCompat mIndexer; private AppInfoComparator mAppNameComparator; private final int mNumAppsPerRow; private int mNumAppRowsInAdapter; @@ -156,7 +151,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) { mAllAppsStore = appsStore; mLauncher = Launcher.getLauncher(context); - mIndexer = new AlphabeticIndexCompat(context); mAppNameComparator = new AppInfoComparator(context); mIsWork = isWork; mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns; @@ -263,7 +257,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator()); for (AppInfo info : mApps) { // Add the section to the cache - String sectionName = getAndUpdateCachedSectionName(info.title); + String sectionName = info.sectionName; // Add it to the mapping ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName); @@ -279,12 +273,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) { mApps.addAll(entry.getValue()); } - } else { - // Just compute the section headers for use below - for (AppInfo info : mApps) { - // Add the section to the cache - getAndUpdateCachedSectionName(info.title); - } } // Recompose the set of adapter items from the current set of apps @@ -320,7 +308,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the // ordered set of sections for (AppInfo info : getFiltersAppInfos()) { - String sectionName = getAndUpdateCachedSectionName(info.title); + String sectionName = info.sectionName; // Create a new section if the section names do not match if (!sectionName.equals(lastSectionName)) { @@ -428,18 +416,4 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { } return result; } - - /** - * Returns the cached section name for the given title, recomputing and updating the cache if - * the title has no cached section name. - */ - private String getAndUpdateCachedSectionName(CharSequence title) { - String sectionName = mCachedSectionNames.get(title); - if (sectionName == null) { - sectionName = mIndexer.computeSectionName(title); - mCachedSectionNames.put(title, sectionName); - } - return sectionName; - } - } diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java index dfdcc7089..46c9006dd 100644 --- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java +++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java @@ -3,7 +3,6 @@ package com.android.launcher3.compat; import android.content.Context; import android.icu.text.AlphabeticIndex; import android.os.LocaleList; -import android.util.Log; import com.android.launcher3.Utilities; @@ -12,28 +11,32 @@ import java.util.Locale; import androidx.annotation.NonNull; public class AlphabeticIndexCompat { - private static final String TAG = "AlphabeticIndexCompat"; private static final String MID_DOT = "\u2219"; - private final BaseIndex mBaseIndex; private final String mDefaultMiscLabel; + private final AlphabeticIndex.ImmutableIndex mBaseIndex; + public AlphabeticIndexCompat(Context context) { - BaseIndex index = null; + this(context.getResources().getConfiguration().getLocales()); + } - try { - index = new AlphabeticIndexVN(context); - } catch (Exception e) { - Log.d(TAG, "Unable to load the system index", e); - } + public AlphabeticIndexCompat(LocaleList locales) { + int localeCount = locales.size(); - mBaseIndex = index == null ? new BaseIndex() : index; + Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0); + AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale); + for (int i = 1; i < localeCount; i++) { + indexBuilder.addLabels(locales.get(i)); + } + indexBuilder.addLabels(Locale.ENGLISH); + mBaseIndex = indexBuilder.buildImmutableIndex(); - if (context.getResources().getConfiguration().locale - .getLanguage().equals(Locale.JAPANESE.getLanguage())) { + if (primaryLocale.getLanguage().equals(Locale.JAPANESE.getLanguage())) { // Japanese character ä»– ("misc") mDefaultMiscLabel = "\u4ed6"; - // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji + // TODO(winsonc, omakoto): We need to handle Japanese sections better, + // especially the kanji } else { // Dot mDefaultMiscLabel = MID_DOT; @@ -45,7 +48,7 @@ public class AlphabeticIndexCompat { */ public String computeSectionName(@NonNull CharSequence cs) { String s = Utilities.trim(cs); - String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s)); + String sectionName = mBaseIndex.getBucket(mBaseIndex.getBucketIndex(s)).getLabel(); if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) { int c = s.codePointAt(0); boolean startsWithDigit = Character.isDigit(c); @@ -66,71 +69,4 @@ public class AlphabeticIndexCompat { } return sectionName; } - - /** - * Base class to support Alphabetic indexing if not supported by the framework. - * TODO(winsonc): disable for non-english locales - */ - private static class BaseIndex { - - private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-"; - private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1; - - /** - * Returns the index of the bucket in which the given string should appear. - */ - protected int getBucketIndex(@NonNull String s) { - if (s.isEmpty()) { - return UNKNOWN_BUCKET_INDEX; - } - int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase()); - if (index != -1) { - return index; - } - return UNKNOWN_BUCKET_INDEX; - } - - /** - * Returns the label for the bucket at the given index (as returned by getBucketIndex). - */ - protected String getBucketLabel(int index) { - return BUCKETS.substring(index, index + 1); - } - } - - /** - * Implementation based on {@link AlphabeticIndex}. - */ - private static class AlphabeticIndexVN extends BaseIndex { - - private final AlphabeticIndex.ImmutableIndex mAlphabeticIndex; - - public AlphabeticIndexVN(Context context) { - LocaleList locales = context.getResources().getConfiguration().getLocales(); - int localeCount = locales.size(); - - Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0); - AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale); - for (int i = 1; i < localeCount; i++) { - indexBuilder.addLabels(locales.get(i)); - } - indexBuilder.addLabels(Locale.ENGLISH); - - mAlphabeticIndex = indexBuilder.buildImmutableIndex(); - } - - /** - * Returns the index of the bucket in which {@param s} should appear. - */ - protected int getBucketIndex(String s) { - return mAlphabeticIndex.getBucketIndex(s); - } - - /** - * Returns the label for the bucket at the given index - */ - protected String getBucketLabel(int index) { - return mAlphabeticIndex.getBucket(index).getLabel(); - } - } } diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java index 32432561b..fc5d11c23 100644 --- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java +++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java @@ -23,19 +23,18 @@ import android.content.Context; import android.os.Bundle; import android.os.UserHandle; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.widget.custom.CustomWidgetParser; +import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.HashMap; import java.util.List; -import androidx.annotation.Nullable; - public abstract class AppWidgetManagerCompat { private static final Object sInstanceLock = new Object(); @@ -63,11 +62,9 @@ public abstract class AppWidgetManagerCompat { } public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) { - if (FeatureFlags.ENABLE_CUSTOM_WIDGETS - && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { - return CustomWidgetParser.getWidgetProvider(mContext, appWidgetId); + if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { + return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(appWidgetId); } - AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId); return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info); } diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java index 106574832..c8b1f67c3 100644 --- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java +++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java @@ -24,12 +24,15 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.widget.custom.CustomWidgetParser; +import com.android.launcher3.widget.custom.CustomAppWidgetProviderInfo; +import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.ArrayList; import java.util.Collections; @@ -37,8 +40,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -import androidx.annotation.Nullable; - class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { private final UserManager mUserManager; @@ -54,14 +55,11 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { return Collections.emptyList(); } if (packageUser == null) { - ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>(); + ArrayList<AppWidgetProviderInfo> providers = new ArrayList<>(); for (UserHandle user : mUserManager.getUserProfiles()) { providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user)); } - - if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) { - providers.addAll(CustomWidgetParser.getCustomWidgets(mContext)); - } + providers.addAll(getCustomWidgets()); return providers; } // Only get providers for the given package/user. @@ -74,9 +72,9 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { } } - if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(packageUser.mUser) + if (Process.myUserHandle().equals(packageUser.mUser) && mContext.getPackageName().equals(packageUser.mPackageName)) { - providers.addAll(CustomWidgetParser.getCustomWidgets(mContext)); + providers.addAll(getCustomWidgets()); } return providers; } @@ -87,9 +85,7 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { if (FeatureFlags.GO_DISABLE_WIDGETS) { return false; } - - if (FeatureFlags.ENABLE_CUSTOM_WIDGETS - && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { + if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { return true; } return mAppWidgetManager.bindAppWidgetIdIfAllowed( @@ -108,9 +104,8 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { } } - if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(user)) { - for (LauncherAppWidgetProviderInfo info : - CustomWidgetParser.getCustomWidgets(mContext)) { + if (Process.myUserHandle().equals(user)) { + for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) { if (info.provider.equals(provider)) { return info; } @@ -131,13 +126,13 @@ class AppWidgetManagerCompatVL extends AppWidgetManagerCompat { result.put(new ComponentKey(info.provider, user), info); } } - - if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) { - for (LauncherAppWidgetProviderInfo info : - CustomWidgetParser.getCustomWidgets(mContext)) { - result.put(new ComponentKey(info.provider, info.getProfile()), info); - } + for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) { + result.put(new ComponentKey(info.provider, info.getProfile()), info); } return result; } + + List<CustomAppWidgetProviderInfo> getCustomWidgets() { + return CustomWidgetManager.INSTANCE.get(mContext).getCustomWidgets(); + } } diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java index b7b0563ba..11ec333d1 100644 --- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java +++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java @@ -19,14 +19,14 @@ package com.android.launcher3.compat; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; +import androidx.annotation.Nullable; + import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.PackageUserKey; import java.util.Collections; import java.util.List; -import androidx.annotation.Nullable; - class AppWidgetManagerCompatVO extends AppWidgetManagerCompatVL { AppWidgetManagerCompatVO(Context context) { diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index 58fc73d23..39f69498b 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -22,30 +22,34 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; +import androidx.annotation.Nullable; + import com.android.launcher3.Utilities; +import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.PackageUserKey; import java.util.List; -import androidx.annotation.Nullable; - public abstract class LauncherAppsCompat { public interface OnAppsChangedCallbackCompat { - void onPackageRemoved(String packageName, UserHandle user); - void onPackageAdded(String packageName, UserHandle user); - void onPackageChanged(String packageName, UserHandle user); - void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing); - void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing); - void onPackagesSuspended(String[] packageNames, UserHandle user); - void onPackagesUnsuspended(String[] packageNames, UserHandle user); - void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts, - UserHandle user); + default void onPackageRemoved(String packageName, UserHandle user) { } + default void onPackageAdded(String packageName, UserHandle user) { } + default void onPackageChanged(String packageName, UserHandle user) { } + default void onPackagesAvailable(String[] packageNames, UserHandle user, + boolean replacing) { } + default void onPackagesUnavailable(String[] packageNames, UserHandle user, + boolean replacing) { } + default void onPackagesSuspended(String[] packageNames, UserHandle user) { } + default void onPackagesUnsuspended(String[] packageNames, UserHandle user) { } + default void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts, + UserHandle user) { } } protected LauncherAppsCompat() { @@ -88,4 +92,8 @@ public abstract class LauncherAppsCompat { @Nullable PackageUserKey packageUser); public abstract List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions(); + + public abstract void registerSessionCallback(LooperExecutor executor, + SessionCallback sessionCallback); + public abstract void unregisterSessionCallback(SessionCallback sessionCallback); } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java index 1885d8f03..281274ce0 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java @@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; @@ -35,6 +36,7 @@ import android.util.Log; import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL; import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; @@ -212,5 +214,17 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() { return mContext.getPackageManager().getPackageInstaller().getAllSessions(); } + + @Override + public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) { + mContext.getPackageManager().getPackageInstaller().registerSessionCallback(sessionCallback, + executor.getHandler()); + } + + @Override + public void unregisterSessionCallback(SessionCallback sessionCallback) { + mContext.getPackageManager().getPackageInstaller() + .unregisterSessionCallback(sessionCallback); + } } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java index 6e7a1bdcf..5e13d00b7 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java @@ -16,6 +16,8 @@ package com.android.launcher3.compat; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; @@ -30,19 +32,17 @@ import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO; import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; import java.util.List; -import androidx.annotation.Nullable; - @TargetApi(26) public class LauncherAppsCompatVO extends LauncherAppsCompatVL { @@ -120,7 +120,7 @@ public class LauncherAppsCompatVO extends LauncherAppsCompatVL { } } else { // Block the worker thread until the accept() is called. - new LooperExecutor(LauncherModel.getWorkerLooper()).execute(new Runnable() { + MODEL_EXECUTOR.execute(new Runnable() { @Override public void run() { try { diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java index 0a1811e34..48805afa0 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java @@ -18,8 +18,10 @@ package com.android.launcher3.compat; import android.annotation.TargetApi; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionCallback; + +import com.android.launcher3.util.LooperExecutor; import java.util.List; @@ -33,4 +35,14 @@ public class LauncherAppsCompatVQ extends LauncherAppsCompatVO { public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() { return mLauncherApps.getAllPackageInstallerSessions(); } + + @Override + public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) { + mLauncherApps.registerPackageInstallerSessionCallback(executor, sessionCallback); + } + + @Override + public void unregisterSessionCallback(SessionCallback sessionCallback) { + mLauncherApps.unregisterPackageInstallerSessionCallback(sessionCallback); + } } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java index 879d963c7..409b21ddc 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -16,13 +16,14 @@ package com.android.launcher3.compat; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; -import android.os.Handler; import android.os.UserHandle; import android.text.TextUtils; import android.util.SparseArray; @@ -31,7 +32,6 @@ import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.Utilities; import com.android.launcher3.icons.IconCache; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; @@ -53,7 +53,6 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { @Thunk final PackageInstaller mInstaller; private final IconCache mCache; - private final Handler mWorker; private final Context mAppContext; private final HashMap<String,Boolean> mSessionVerifiedMap = new HashMap<>(); private final LauncherAppsCompat mLauncherApps; @@ -63,11 +62,10 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { mAppContext = context.getApplicationContext(); mInstaller = context.getPackageManager().getPackageInstaller(); mCache = LauncherAppState.getInstance(context).getIconCache(); - mWorker = new Handler(LauncherModel.getWorkerLooper()); - mInstaller.registerSessionCallback(mCallback, mWorker); mLauncherApps = LauncherAppsCompat.getInstance(context); - mPromiseIconIds = IntSet.wrap(IntArray.wrap(Utilities.getIntArrayFromString( - getPrefs(context).getString(PROMISE_ICON_IDS, "")))); + mLauncherApps.registerSessionCallback(MODEL_EXECUTOR, mCallback); + mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString( + getPrefs(context).getString(PROMISE_ICON_IDS, ""))); cleanUpPromiseIconIds(); } @@ -127,7 +125,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { @Override public void onStop() { - mInstaller.unregisterSessionCallback(mCallback); + mLauncherApps.unregisterSessionCallback(mCallback); } @Thunk void sendUpdate(PackageInstallInfo info) { @@ -223,12 +221,14 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { private SessionInfo pushSessionDisplayToLauncher(int sessionId) { SessionInfo session = verify(mInstaller.getSessionInfo(sessionId)); if (session != null && session.getAppPackageName() != null) { + UserHandle user = getUserHandle(session); mActiveSessions.put(session.getSessionId(), - new PackageUserKey(session.getAppPackageName(), getUserHandle(session))); - addSessionInfoToCache(session, getUserHandle(session)); + new PackageUserKey(session.getAppPackageName(), user)); + addSessionInfoToCache(session, user); LauncherAppState app = LauncherAppState.getInstanceNoCreate(); if (app != null) { - app.getModel().updateSessionDisplayInfo(session.getAppPackageName()); + app.getModel().updateSessionDisplayInfo(session.getAppPackageName(), + user); } return session; } diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 025087b9d..64d236fab 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -23,11 +23,11 @@ import android.content.SharedPreferences; import androidx.annotation.GuardedBy; import androidx.annotation.Keep; - import androidx.annotation.VisibleForTesting; -import com.android.launcher3.Utilities; +import com.android.launcher3.Utilities; import com.android.launcher3.uioverrides.TogglableFlag; + import java.util.ArrayList; import java.util.List; import java.util.SortedMap; @@ -74,9 +74,6 @@ public abstract class BaseFlags { //Feature flag to enable pulling down navigation shade from workspace. public static final boolean PULL_DOWN_STATUS_BAR = true; - // When true, custom widgets are loaded using CustomWidgetParser. - public static final boolean ENABLE_CUSTOM_WIDGETS = false; - // Features to control Launcher3Go behavior public static final boolean GO_DISABLE_WIDGETS = false; @@ -111,10 +108,21 @@ public abstract class BaseFlags { "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout"); + public static final TogglableFlag FOLDER_NAME_SUGGEST = new TogglableFlag( + "FOLDER_NAME_SUGGEST", true, + "Suggests folder names instead of blank text."); + public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag( "APP_SEARCH_IMPROVEMENTS", false, "Adds localized title and keyword search and ranking"); + public static final TogglableFlag ENABLE_PREDICTION_DISMISS = new TogglableFlag( + "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list"); + + public static final TogglableFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag( + "ASSISTANT_GIVES_LAUNCHER_FOCUS", false, + "Allow Launcher to handle nav bar gestures while Assistant is running over it"); + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) { diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java index a2dcbf87d..9fb10905f 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -20,6 +20,7 @@ import static com.android.launcher3.logging.LoggerUtils.newCommandAction; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.logging.LoggerUtils.newItemTarget; import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.annotation.TargetApi; import android.app.ActivityOptions; @@ -47,7 +48,6 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetProviderInfo; -import com.android.launcher3.LauncherModel; import com.android.launcher3.R; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompatVO; @@ -55,7 +55,6 @@ import com.android.launcher3.model.WidgetItem; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.InstantAppResolver; -import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; @@ -234,7 +233,7 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); mWidgetCell.ensurePreview(); } - }.executeOnExecutor(new LooperExecutor(LauncherModel.getWorkerLooper())); + }.executeOnExecutor(MODEL_EXECUTOR); // TODO: Create a worker looper executor and reuse that everywhere. } diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index b59164ae0..cdc70611d 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -284,7 +284,8 @@ public class DragLayer extends BaseDragLayer<Launcher> { // The child may be scaled (always about the center of the view) so to account for it, // we have to offset the position by the scaled size. Once we do that, we can center // the drag view about the scaled child view. - toY += Math.round(toScale * tv.getPaddingTop()); + // padding will remain constant (does not scale with size) + toY += tv.getPaddingTop(); toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2; if (dragView.getDragVisualizeOffset() != null) { toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y); diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 09c5e5b2b..f66d07e3d 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -17,6 +17,7 @@ package com.android.launcher3.dragndrop; import static com.android.launcher3.Utilities.getBadge; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -41,16 +42,19 @@ import android.os.Handler; import android.os.Looper; import android.view.View; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + import com.android.launcher3.FastBitmapDrawable; +import com.android.launcher3.FirstFrameAnimatorHelper; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.FirstFrameAnimatorHelper; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.util.Themes; @@ -58,10 +62,6 @@ import com.android.launcher3.util.Thunk; import java.util.Arrays; -import androidx.dynamicanimation.animation.FloatPropertyCompat; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - public class DragView extends View implements LauncherStateManager.StateListener { private static final ColorMatrix sTempMatrix1 = new ColorMatrix(); private static final ColorMatrix sTempMatrix2 = new ColorMatrix(); @@ -210,7 +210,7 @@ public class DragView extends View implements LauncherStateManager.StateListener return; } // Load the adaptive icon on a background thread and add the view in ui thread. - new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(new Runnable() { + MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(new Runnable() { @Override public void run() { Object[] outObj = new Object[1]; diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java index 589ad25e4..06b5c409d 100644 --- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java +++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java @@ -106,6 +106,7 @@ public class FlingToDeleteHelper { * @return the vector at which the item was flung, or null if no fling was detected. */ private PointF isFlingingToDelete() { + if (mVelocityTracker == null) return null; if (mDropTarget == null) { mDropTarget = (ButtonDropTarget) mLauncher.findViewById(R.id.delete_target_text); } diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java index d8a1f9951..0bb3fbac5 100644 --- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java +++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java @@ -16,6 +16,8 @@ package com.android.launcher3.dragndrop; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -31,7 +33,6 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.launcher3.Launcher; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.folder.PreviewBackground; @@ -85,7 +86,7 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { // Create the actual drawable on the UI thread to avoid race conditions with // FolderIcon draw pass try { - return new MainThreadExecutor().submit(() -> { + return MAIN_EXECUTOR.submit(() -> { FolderIcon icon = launcher.findFolderIcon(folderId); return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize); }).get(); diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index f22b53338..6b9e20525 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -26,12 +26,12 @@ import android.animation.AnimatorSet; import android.annotation.SuppressLint; import android.appwidget.AppWidgetHostView; import android.content.Context; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.Rect; import android.text.InputType; import android.text.Selection; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; @@ -74,7 +74,10 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.pageindicators.PageIndicatorDots; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ClipPathView; @@ -126,9 +129,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private static final Rect sTempRect = new Rect(); private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10; - private static String sDefaultFolderName; - private static String sHintText; - private final Alarm mReorderAlarm = new Alarm(); private final Alarm mOnExitAlarm = new Alarm(); private final Alarm mOnScrollHintAlarm = new Alarm(); @@ -173,8 +173,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private boolean mDeleteFolderOnDropCompleted = false; private boolean mSuppressFolderDeletion = false; private boolean mItemAddedBackToSelfViaIcon = false; - @Thunk float mFolderIconPivotX; - @Thunk float mFolderIconPivotY; private boolean mIsEditingName = false; @ViewDebug.ExportedProperty(category = "launcher") @@ -196,8 +194,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo super(context, attrs); setAlwaysDrawnWithCacheEnabled(false); - setLocaleDependentFields(getResources(), false /* force */); - mLauncher = Launcher.getLauncher(context); // We need this view to be focusable in touch mode so that when text editing of the folder // name is complete, we have something to focus on, thus hiding the cursor and giving @@ -315,10 +311,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Convert to a string here to ensure that no other state associated with the text field // gets saved. String newTitle = mFolderName.getText().toString(); - mInfo.setTitle(newTitle); + mInfo.title = newTitle; + mFolderIcon.onTitleChanged(newTitle); mLauncher.getModelWriter().updateItemInDatabase(mInfo); - mFolderName.setHint(sDefaultFolderName.contentEquals(newTitle) ? sHintText : null); + if (TextUtils.isEmpty(mInfo.title)) { + mFolderName.setHint(R.string.folder_hint_text); + } else { + mFolderName.setHint(null); + } sendCustomAccessibilityEvent( this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, @@ -385,7 +386,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mInfo = info; ArrayList<WorkspaceItemInfo> children = info.contents; Collections.sort(children, ITEM_POS_COMPARATOR); - mContent.bindItems(children); + updateItemLocationsInDatabaseBatch(); DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); if (lp == null) { @@ -393,30 +394,40 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.customPosition = true; setLayoutParams(lp); } - centerAboutIcon(); - mItemsInvalidated = true; - updateTextViewFocus(); mInfo.addListener(this); - if (!sDefaultFolderName.contentEquals(mInfo.title)) { + if (!TextUtils.isEmpty(mInfo.title)) { mFolderName.setText(mInfo.title); mFolderName.setHint(null); } else { mFolderName.setText(""); - mFolderName.setHint(sHintText); + mFolderName.setHint(R.string.folder_hint_text); } - // In case any children didn't come across during loading, clean up the folder accordingly - mFolderIcon.post(new Runnable() { - public void run() { - if (getItemCount() <= 1) { - replaceFolderWithFinalItem(); - } + mFolderIcon.post(() -> { + if (getItemCount() <= 1) { + replaceFolderWithFinalItem(); } }); } + + /** + * Show suggested folder title. + */ + public void showSuggestedTitle(CharSequence suggestName) { + if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) { + if (!TextUtils.isEmpty(suggestName)) { + mFolderName.setHint(suggestName); + mFolderName.setText(suggestName); + mFolderName.showKeyboard(); + mInfo.title = suggestName; + } + animateOpen(); + } + } + /** * Creates a new UserFolder, inflated from R.layout.user_folder. * @@ -473,17 +484,49 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } /** + * Opens the folder as part of a drag operation + */ + public void beginExternalDrag() { + mIsExternalDrag = true; + mDragInProgress = true; + + // Since this folder opened by another controller, it might not get onDrop or + // onDropComplete. Perform cleanup once drag-n-drop ends. + mDragController.addDragListener(this); + + ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.contents); + mEmptyCellRank = items.size(); + items.add(null); // Add an empty spot at the end + + animateOpen(items, mEmptyCellRank / mContent.itemsPerPage()); + } + + /** * 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() { + animateOpen(mInfo.contents, 0); + } + + /** + * 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. + */ + private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) { Folder openFolder = getOpen(mLauncher); if (openFolder != null && openFolder != this) { // Close any open folder before opening a folder. openFolder.close(true); } + mContent.bindItems(items); + centerAboutIcon(); + mItemsInvalidated = true; + updateTextViewFocus(); + mIsOpen = true; DragLayer dragLayer = mLauncher.getDragLayer(); @@ -500,18 +543,13 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } mContent.completePendingPageChanges(); - if (!mDragInProgress) { - // Open on the first page. - mContent.snapToPageImmediately(0); - } + mContent.snapToPageImmediately(pageNo); // 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; - centerAboutIcon(); - AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator(); anim.addListener(new AnimatorListenerAdapter() { @Override @@ -524,7 +562,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mState = STATE_OPEN; announceAccessibilityChanges(); - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("folder opened"); + mLauncher.getUserEventDispatcher().logActionOnItem( + Touch.TAP, + Direction.NONE, + ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY); + mContent.setFocusOnFirstChild(); } }); @@ -570,20 +612,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (mDragController.isDragging()) { mDragController.forceTouchMove(); } - mContent.verifyVisibleHighResIcons(mContent.getNextPage()); } - public void beginExternalDrag() { - mEmptyCellRank = mContent.allocateRankForNewItem(); - mIsExternalDrag = true; - mDragInProgress = true; - - // Since this folder opened by another controller, it might not get onDrop or - // onDropComplete. Perform cleanup once drag-n-drop ends. - mDragController.addDragListener(this); - } - @Override protected boolean isOfType(int type) { return (type & TYPE_FOLDER) != 0; @@ -668,6 +699,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } else if (mDragInProgress) { mDeleteFolderOnDropCompleted = true; } + } else if (!mDragInProgress) { + mContent.unbindItems(); } mSuppressFolderDeletion = false; clearDragInfo(); @@ -822,9 +855,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } } + @Override public void onDropCompleted(final View target, final DragObject d, final boolean success) { - if (success) { if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { replaceFolderWithFinalItem(); @@ -834,9 +867,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo WorkspaceItemInfo info = (WorkspaceItemInfo) d.dragInfo; View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) ? mCurrentDragView : mContent.createNewView(info); - ArrayList<View> views = getItemsInReadingOrder(); + ArrayList<View> views = getIconsInReadingOrder(); views.add(info.rank, icon); - mContent.arrangeChildren(views, views.size()); + mContent.arrangeChildren(views); mItemsInvalidated = true; try (SuppressInfoChanges s = new SuppressInfoChanges()) { @@ -863,7 +896,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Reordering may have occured, and we need to save the new item locations. We do this once // at the end to prevent unnecessary database operations. updateItemLocationsInDatabaseBatch(); - // Use the item count to check for multi-page as the folder UI may not have // been refreshed yet. if (getItemCount() <= mContent.itemsPerPage()) { @@ -874,16 +906,21 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } private void updateItemLocationsInDatabaseBatch() { - ArrayList<View> list = getItemsInReadingOrder(); - ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); - for (int i = 0; i < list.size(); i++) { - View v = list.get(i); - ItemInfo info = (ItemInfo) v.getTag(); - info.rank = i; - items.add(info); + FolderGridOrganizer verifier = new FolderGridOrganizer( + mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo); + + ArrayList<ItemInfo> items = new ArrayList<>(); + int total = mInfo.contents.size(); + for (int i = 0; i < total; i++) { + WorkspaceItemInfo itemInfo = mInfo.contents.get(i); + if (verifier.updateRankAndPos(itemInfo, i)) { + items.add(itemInfo); + } } - mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0); + if (!items.isEmpty()) { + mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0); + } } public void notifyDrop() { @@ -948,28 +985,16 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo setPivotX(folderPivotX); setPivotY(folderPivotY); - mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * - (1.0f * folderPivotX / width)); - mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * - (1.0f * folderPivotY / height)); - lp.width = width; lp.height = height; lp.x = left; lp.y = top; } - public float getPivotXForIconAnimation() { - return mFolderIconPivotX; - } - public float getPivotYForIconAnimation() { - return mFolderIconPivotY; - } - private int getContentAreaHeight() { DeviceProfile grid = mLauncher.getDeviceProfile(); - int maxContentAreaHeight = grid.availableHeightPx - - grid.getTotalWorkspacePadding().y - mFooterHeight; + int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y + - mFooterHeight; int height = Math.min(maxContentAreaHeight, mContent.getDesiredHeight()); return Math.max(height, MIN_CONTENT_DIMEN); @@ -1021,22 +1046,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo * Rearranges the children based on their rank. */ public void rearrangeChildren() { - rearrangeChildren(-1); - } - - /** - * Rearranges the children based on their rank. - * @param itemCount if greater than the total children count, empty spaces are left at the end, - * otherwise it is ignored. - */ - public void rearrangeChildren(int itemCount) { - ArrayList<View> views = getItemsInReadingOrder(); - mContent.arrangeChildren(views, Math.max(itemCount, views.size())); + mContent.arrangeChildren(getIconsInReadingOrder()); mItemsInvalidated = true; } public int getItemCount() { - return mContent.getItemCount(); + return mInfo.contents.size(); } @Thunk void replaceFolderWithFinalItem() { @@ -1044,7 +1059,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo Runnable onCompleteRunnable = new Runnable() { @Override public void run() { - int itemCount = mInfo.contents.size(); + int itemCount = getItemCount(); if (itemCount <= 1) { View newIcon = null; @@ -1121,9 +1136,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return false; } }); + } else { + setOnKeyListener(null); } } + @Override public void onDrop(DragObject d, DragOptions options) { // If the icon was dropped while the page was being scrolled, we need to compute // the target location again such that the icon is placed of the final page. @@ -1171,12 +1189,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // before creating the view, so that WorkspaceItemInfo is updated appropriately. mLauncher.getModelWriter().addOrMoveItemInDatabase( si, mInfo.id, 0, si.cellX, si.cellY); - - // We only need to update the locations if it doesn't get handled in - // #onDropCompleted. - if (d.dragSource != this) { - updateItemLocationsInDatabaseBatch(); - } mIsExternalDrag = false; } else { currentDragView = mCurrentDragView; @@ -1203,7 +1215,13 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Temporarily suppress the listener, as we did all the work already here. try (SuppressInfoChanges s = new SuppressInfoChanges()) { - mInfo.add(si, false); + mInfo.add(si, mEmptyCellRank, false); + } + + // We only need to update the locations if it doesn't get handled in + // #onDropCompleted. + if (d.dragSource != this) { + updateItemLocationsInDatabaseBatch(); } } @@ -1226,22 +1244,29 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // to correspond to the animation of the icon back into the folder. This is public void hideItem(WorkspaceItemInfo info) { View v = getViewForInfo(info); - v.setVisibility(INVISIBLE); + if (v != null) { + v.setVisibility(INVISIBLE); + } } public void showItem(WorkspaceItemInfo info) { View v = getViewForInfo(info); - v.setVisibility(VISIBLE); + if (v != null) { + v.setVisibility(VISIBLE); + } } @Override public void onAdd(WorkspaceItemInfo item, int rank) { - View view = mContent.createAndAddViewForRank(item, rank); + FolderGridOrganizer verifier = new FolderGridOrganizer( + mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo); + verifier.updateRankAndPos(item, rank); mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, item.cellY); + updateItemLocationsInDatabaseBatch(); - ArrayList<View> items = new ArrayList<>(getItemsInReadingOrder()); - items.add(rank, view); - mContent.arrangeChildren(items, items.size()); + if (mContent.areViewsBound()) { + mContent.createAndAddViewForRank(item, rank); + } mItemsInvalidated = true; } @@ -1264,13 +1289,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } private View getViewForInfo(final WorkspaceItemInfo item) { - return mContent.iterateOverItems(new ItemOperator() { - - @Override - public boolean evaluate(ItemInfo info, View view) { - return info == item; - } - }); + return mContent.iterateOverItems((info, view) -> info == item); } @Override @@ -1278,32 +1297,27 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo updateTextViewFocus(); } - @Override - public void prepareAutoUpdate() { - close(false); - } - - public void onTitleChanged(CharSequence title) { + /** + * Utility methods to iterate over items of the view + */ + public void iterateOverItems(ItemOperator op) { + mContent.iterateOverItems(op); } - public ArrayList<View> getItemsInReadingOrder() { + /** + * Returns the sorted list of all the icons in the folder + */ + public ArrayList<View> getIconsInReadingOrder() { if (mItemsInvalidated) { mItemsInReadingOrder.clear(); - mContent.iterateOverItems(new ItemOperator() { - - @Override - public boolean evaluate(ItemInfo info, View view) { - mItemsInReadingOrder.add(view); - return false; - } - }); + mContent.iterateOverItems((i, v) -> !mItemsInReadingOrder.add(v)); mItemsInvalidated = false; } return mItemsInReadingOrder; } public List<BubbleTextView> getItemsOnPage(int page) { - ArrayList<View> allItems = getItemsInReadingOrder(); + ArrayList<View> allItems = getIconsInReadingOrder(); int lastPage = mContent.getPageCount() - 1; int totalItemsInFolder = allItems.size(); int itemsPerPage = mContent.itemsPerPage(); @@ -1482,15 +1496,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return false; } - public static void setLocaleDependentFields(Resources res, boolean force) { - if (sDefaultFolderName == null || force) { - sDefaultFolderName = res.getString(R.string.folder_name); - } - if (sHintText == null || force) { - sHintText = res.getString(R.string.folder_hint_text); - } - } - /** * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of * rounded rect. diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 962f21560..9eb06938a 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -79,7 +79,7 @@ public class FolderAnimationManager { private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator; private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); - + private final FolderGridOrganizer mPreviewVerifier; public FolderAnimationManager(Folder folder, boolean isOpening) { mFolder = folder; @@ -91,6 +91,7 @@ public class FolderAnimationManager { mContext = folder.getContext(); mLauncher = folder.mLauncher; + mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv); mIsOpening = isOpening; @@ -113,7 +114,7 @@ public class FolderAnimationManager { public AnimatorSet getAnimator() { final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams(); ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule(); - final List<BubbleTextView> itemsInPreview = mFolderIcon.getPreviewItems(); + final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0); // Match position of the FolderIcon final Rect folderIconPos = new Rect(); @@ -226,15 +227,22 @@ public class FolderAnimationManager { } /** + * Returns the list of "preview items" on {@param page}. + */ + private List<BubbleTextView> getPreviewIconsOnPage(int page) { + return mPreviewVerifier.setFolderInfo(mFolder.mInfo) + .previewItemsForPage(page, mFolder.getIconsInReadingOrder()); + } + + /** * Animate the items on the current page. */ private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale, int previewItemOffsetX, int previewItemOffsetY) { ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule(); boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0; - final List<BubbleTextView> itemsInPreview = isOnFirstPage - ? mFolderIcon.getPreviewItems() - : mFolderIcon.getPreviewItemsOnPage(mFolder.mContent.getCurrentPage()); + final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage( + isOnFirstPage ? 0 : mFolder.mContent.getCurrentPage()); final int numItemsInPreview = itemsInPreview.size(); final int numItemsInFirstPagePreview = isOnFirstPage ? numItemsInPreview : MAX_NUM_ITEMS_IN_PREVIEW; diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java new file mode 100644 index 000000000..9d14a5ffb --- /dev/null +++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2019 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 static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; + +import android.graphics.Point; + +import com.android.launcher3.FolderInfo; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for managing item positions in a folder based on rank + */ +public class FolderGridOrganizer { + + private final Point mPoint = new Point(); + private final int mMaxCountX; + private final int mMaxCountY; + private final int mMaxItemsPerPage; + + private int mNumItemsInFolder; + private int mCountX; + private int mCountY; + private boolean mDisplayingUpperLeftQuadrant = false; + + /** + * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work. + */ + public FolderGridOrganizer(InvariantDeviceProfile profile) { + mMaxCountX = profile.numFolderColumns; + mMaxCountY = profile.numFolderRows; + mMaxItemsPerPage = mMaxCountX * mMaxCountY; + } + + /** + * Updates the organizer with the provided folder info + */ + public FolderGridOrganizer setFolderInfo(FolderInfo info) { + return setContentSize(info.contents.size()); + } + + /** + * Updates the organizer to reflect the content size + */ + public FolderGridOrganizer setContentSize(int contentSize) { + if (contentSize != mNumItemsInFolder) { + calculateGridSize(contentSize); + + mDisplayingUpperLeftQuadrant = contentSize > MAX_NUM_ITEMS_IN_PREVIEW; + mNumItemsInFolder = contentSize; + } + return this; + } + + public int getCountX() { + return mCountX; + } + + public int getCountY() { + return mCountY; + } + + public int getMaxItemsPerPage() { + return mMaxItemsPerPage; + } + + /** + * 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 calculateGridSize(int count) { + boolean done; + int gridCountX = mCountX; + int gridCountY = mCountY; + + if (count >= mMaxItemsPerPage) { + gridCountX = mMaxCountX; + gridCountY = mMaxCountY; + done = true; + } else { + done = false; + } + + while (!done) { + int oldCountX = gridCountX; + int oldCountY = gridCountY; + if (gridCountX * gridCountY < count) { + // Current grid is too small, expand it + if ((gridCountX <= gridCountY || gridCountY == mMaxCountY) + && gridCountX < mMaxCountX) { + gridCountX++; + } else if (gridCountY < mMaxCountY) { + gridCountY++; + } + 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 = gridCountX == oldCountX && gridCountY == oldCountY; + } + + mCountX = gridCountX; + mCountY = gridCountY; + } + + /** + * Updates the item's cellX, cellY and rank corresponding to the provided rank. + * @return true if there was any change + */ + public boolean updateRankAndPos(ItemInfo item, int rank) { + Point pos = getPosForRank(rank); + if (!pos.equals(item.cellX, item.cellY) || rank != item.rank) { + item.rank = rank; + item.cellX = pos.x; + item.cellY = pos.y; + return true; + } + return false; + } + + /** + * Returns the position of the item in the grid + */ + public Point getPosForRank(int rank) { + int pagePos = rank % mMaxItemsPerPage; + mPoint.x = pagePos % mCountX; + mPoint.y = pagePos / mCountX; + return mPoint; + } + + /** + * Returns the preview items for the provided pageNo using the full list of contents + */ + public <T, R extends T> ArrayList<R> previewItemsForPage(int page, List<T> contents) { + ArrayList<R> result = new ArrayList<>(); + int itemsPerPage = mCountX * mCountY; + int start = itemsPerPage * page; + int end = Math.min(start + itemsPerPage, contents.size()); + + for (int i = start, rank = 0; i < end; i++, rank++) { + if (isItemInPreview(page, rank)) { + result.add((R) contents.get(i)); + } + + if (result.size() == MAX_NUM_ITEMS_IN_PREVIEW) { + break; + } + } + return result; + } + + /** + * Returns whether the item with rank is in the default Folder icon preview. + */ + public boolean isItemInPreview(int rank) { + return isItemInPreview(0, rank); + } + + /** + * @param page The page the item is on. + * @param rank The rank of the item. + * @return True iff the icon is in the 2x2 upper left quadrant of the Folder. + */ + public boolean isItemInPreview(int page, int rank) { + // First page items are laid out such that the first 4 items are always in the upper + // left quadrant. For all other pages, we need to check the row and col. + if (page > 0 || mDisplayingUpperLeftQuadrant) { + int col = rank % mCountX; + int row = rank / mCountX; + return col < 2 && row < 2; + } + return rank < MAX_NUM_ITEMS_IN_PREVIEW; + } +} diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 0e2d4673e..fd6d1e38a 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -58,18 +58,21 @@ import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.dragndrop.BaseItemDragListener; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.touch.ItemClickHandler; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.IconLabelDotView; import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; /** * An icon that can appear on in the workspace representing an {@link Folder}. @@ -96,11 +99,11 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel PreviewBackground mBackground = new PreviewBackground(); private boolean mBackgroundIsVisible = true; - FolderIconPreviewVerifier mPreviewVerifier; + FolderGridOrganizer mPreviewVerifier; ClippedFolderIconLayoutRule mPreviewLayoutRule; private PreviewItemManager mPreviewItemManager; private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); - private List<BubbleTextView> mCurrentPreviewItems = new ArrayList<>(); + private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>(); boolean mAnimating = false; @@ -175,7 +178,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel icon.setOnClickListener(ItemClickHandler.INSTANCE); icon.mInfo = folderInfo; icon.mLauncher = launcher; - icon.mDotRenderer = grid.mDotRenderer; + icon.mDotRenderer = grid.mDotRendererWorkSpace; icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title)); Folder folder = Folder.fromXml(launcher); folder.setDragController(launcher.getDragController()); @@ -214,7 +217,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel private void setFolder(Folder folder) { mFolder = folder; - mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); + mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv); mPreviewVerifier.setFolderInfo(mFolder.getInfo()); updatePreviewItems(false); } @@ -232,11 +235,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } public void addItem(WorkspaceItemInfo item) { - addItem(item, true); - } - - public void addItem(WorkspaceItemInfo item, boolean animate) { - mInfo.add(item, animate); + mInfo.add(item, true); } public void removeItem(WorkspaceItemInfo item, boolean animate) { @@ -261,7 +260,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel OnAlarmListener mOnOpenListener = new OnAlarmListener() { public void onAlarm(Alarm alarm) { mFolder.beginExternalDrag(); - mFolder.animateOpen(); } }; @@ -296,8 +294,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } private void onDrop(final WorkspaceItemInfo item, DragView animateView, Rect finalRect, - float scaleRelativeToDragLayer, int index, - boolean itemReturnedOnFailedDrop) { + float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) { item.cellX = -1; item.cellY = -1; @@ -328,18 +325,17 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1); boolean itemAdded = false; if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) { - List<BubbleTextView> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); - addItem(item, false); + List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); + mInfo.add(item, index, false); mCurrentPreviewItems.clear(); - mCurrentPreviewItems.addAll(getPreviewItems()); + mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0)); if (!oldPreviewItems.equals(mCurrentPreviewItems)) { - for (int i = 0; i < mCurrentPreviewItems.size(); ++i) { - if (mCurrentPreviewItems.get(i).getTag().equals(item)) { - // If the item dropped is going to be in the preview, we update the - // index here to reflect its position in the preview. - index = i; - } + int newIndex = mCurrentPreviewItems.indexOf(item); + if (newIndex >= 0) { + // If the item dropped is going to be in the preview, we update the + // index here to reflect its position in the preview. + index = newIndex; } mPreviewItemManager.hidePreviewItem(index, true); @@ -351,13 +347,13 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } if (!itemAdded) { - addItem(item); + mInfo.add(item, index, true); } int[] center = new int[2]; float scale = getLocalCenterForIndex(index, numItemsInPreview, center); - center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]); - center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]); + center[0] = Math.round(scaleRelativeToDragLayer * center[0]); + center[1] = Math.round(scaleRelativeToDragLayer * center[1]); to.offset(center[0] - animateView.getMeasuredWidth() / 2, center[1] - animateView.getMeasuredHeight() / 2); @@ -374,12 +370,17 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true); final int finalIndex = index; - postDelayed(new Runnable() { - public void run() { - mPreviewItemManager.hidePreviewItem(finalIndex, false); - mFolder.showItem(item); - invalidate(); - } + + String[] suggestedNameOut = new String[1]; + if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) { + Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider() + .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut)); + } + postDelayed(() -> { + mPreviewItemManager.hidePreviewItem(finalIndex, false); + mFolder.showItem(item); + invalidate(); + mFolder.showSuggestedTitle(suggestedNameOut[0]); }, DROP_IN_ANIMATION_DURATION); } else { addItem(item); @@ -398,7 +399,8 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel item = (WorkspaceItemInfo) d.dragInfo; } mFolder.notifyDrop(); - onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), + onDrop(item, d.dragView, null, 1.0f, + itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(), itemReturnedOnFailedDrop); } @@ -510,8 +512,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mBackground.drawBackground(canvas); } - if (mFolder == null) return; - if (mFolder.getItemCount() == 0 && !mAnimating) return; + if (mCurrentPreviewItems.isEmpty() && !mAnimating) return; final int saveCount = canvas.save(); canvas.clipPath(mBackground.getClipPath()); @@ -553,31 +554,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } /** - * Returns the list of preview items displayed in the icon. + * Returns the list of items which should be visible in the preview */ - public List<BubbleTextView> getPreviewItems() { - return getPreviewItemsOnPage(0); - } - - /** - * Returns the list of "preview items" on {@param page}. - */ - public List<BubbleTextView> getPreviewItemsOnPage(int page) { - mPreviewVerifier.setFolderInfo(mFolder.getInfo()); - - List<BubbleTextView> itemsToDisplay = new ArrayList<>(); - List<BubbleTextView> itemsOnPage = mFolder.getItemsOnPage(page); - int numItems = itemsOnPage.size(); - for (int rank = 0; rank < numItems; ++rank) { - if (mPreviewVerifier.isItemInPreview(page, rank)) { - itemsToDisplay.add(itemsOnPage.get(rank)); - } - - if (itemsToDisplay.size() == MAX_NUM_ITEMS_IN_PREVIEW) { - break; - } - } - return itemsToDisplay; + public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) { + return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents); } @Override @@ -595,11 +575,14 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel private void updatePreviewItems(boolean animate) { mPreviewItemManager.updatePreviewItems(animate); mCurrentPreviewItems.clear(); - mCurrentPreviewItems.addAll(getPreviewItems()); + mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0)); } - @Override - public void prepareAutoUpdate() { + /** + * Updates the preview items which match the provided condition + */ + public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) { + mPreviewItemManager.updatePreviewItems(itemCheck); } @Override @@ -622,7 +605,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel requestLayout(); } - @Override public void onTitleChanged(CharSequence title) { mFolderName.setText(title); setContentDescription(getContext().getString(R.string.folder_name_format, title)); diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java deleted file mode 100644 index 4c84e354d..000000000 --- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.folder; - -import android.util.Log; - -import com.android.launcher3.FolderInfo; -import com.android.launcher3.InvariantDeviceProfile; - -import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; - -/** - * Verifies whether an item in a Folder is displayed in the FolderIcon preview. - */ -public class FolderIconPreviewVerifier { - - private static final String TAG = "FolderPreviewVerifier"; - - private final int mMaxGridCountX; - private final int mMaxGridCountY; - private final int mMaxItemsPerPage; - private final int[] mGridSize = new int[] { 1, 1 }; - - private int mNumItemsInFolder; - private int mGridCountX; - private boolean mDisplayingUpperLeftQuadrant = false; - - /** - * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work. - */ - public FolderIconPreviewVerifier(InvariantDeviceProfile profile) { - mMaxGridCountX = profile.numFolderColumns; - mMaxGridCountY = profile.numFolderRows; - mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY; - } - - public void setFolderInfo(FolderInfo info) { - int numItemsInFolder = info.contents.size(); - if (numItemsInFolder != mNumItemsInFolder) { - FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX, - mMaxGridCountY, mMaxItemsPerPage, mGridSize); - mGridCountX = mGridSize[0]; - - mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW; - mNumItemsInFolder = numItemsInFolder; - } - } - - /** - * Returns whether the item with {@param rank} is in the default Folder icon preview. - */ - public boolean isItemInPreview(int rank) { - return isItemInPreview(0, rank); - } - - /** - * @param page The page the item is on. - * @param rank The rank of the item. - * @return True iff the icon is in the 2x2 upper left quadrant of the Folder. - */ - public boolean isItemInPreview(int page, int rank) { - if (mGridSize[0] == 1) { - Log.w(TAG, "setFolderInfo not called before checking if item is in preview."); - } - - // First page items are laid out such that the first 4 items are always in the upper - // left quadrant. For all other pages, we need to check the row and col. - if (page > 0 || mDisplayingUpperLeftQuadrant) { - int col = rank % mGridCountX; - int row = rank / mGridCountX; - return col < 2 && row < 2; - } - return rank < MAX_NUM_ITEMS_IN_PREVIEW; - } -} diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java new file mode 100644 index 000000000..0a1221e69 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderNameProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 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.content.ComponentName; +import android.content.Context; + +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.WorkspaceItemInfo; + +import java.util.ArrayList; + +/** + * Locates provider for the folder name. + */ +public class FolderNameProvider { + + /** + * Returns suggested folder name. + */ + public CharSequence getSuggestedFolderName(Context context, + ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] suggestName) { + // Currently only run the algorithm on initial folder creation. + // For more than 2 items in the folder, the ranking algorithm for finding + // candidate folder name should be rewritten. + if (workspaceItemInfos.size() == 2) { + ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent(); + ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent(); + + String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName(); + String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName(); + // If the two icons are from the same package, + // then assign the main icon's name + if (pkgName0.equals(pkgName1)) { + WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0); + WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1); + if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) { + suggestName[0] = wInfo0.title; + } else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) { + suggestName[0] = wInfo1.title; + } + return suggestName[0]; + // two icons are all shortcuts. Don't assign title + } + } + return suggestName[0]; + } +} diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 57105e7ca..3b5fd5902 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -24,10 +24,10 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewDebug; +import com.android.launcher3.BaseActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; @@ -38,18 +38,22 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; -import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.ViewCache; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.function.ToIntFunction; +import java.util.stream.Collectors; public class FolderPagedView extends PagedView<PageIndicatorDots> { @@ -68,17 +72,12 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { public final boolean mIsRtl; - private final LayoutInflater mInflater; private final ViewGroupFocusHelper mFocusIndicatorHelper; @Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>(); - @ViewDebug.ExportedProperty(category = "launcher") - private final int mMaxCountX; - @ViewDebug.ExportedProperty(category = "launcher") - private final int mMaxCountY; - @ViewDebug.ExportedProperty(category = "launcher") - private final int mMaxItemsPerPage; + private final FolderGridOrganizer mOrganizer; + private final ViewCache mViewCache; private int mAllocatedContentSize; @ViewDebug.ExportedProperty(category = "launcher") @@ -88,20 +87,20 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { private Folder mFolder; + // If the views are attached to the folder or not. A folder should be bound when its + // animating or is open. + private boolean mViewsBound = false; + public FolderPagedView(Context context, AttributeSet attrs) { super(context, attrs); InvariantDeviceProfile profile = LauncherAppState.getIDP(context); - mMaxCountX = profile.numFolderColumns; - mMaxCountY = profile.numFolderRows; - - mMaxItemsPerPage = mMaxCountX * mMaxCountY; - - mInflater = LayoutInflater.from(context); + mOrganizer = new FolderGridOrganizer(profile); mIsRtl = Utilities.isRtl(getResources()); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); mFocusIndicatorHelper = new ViewGroupFocusHelper(this); + mViewCache = BaseActivity.fromContext(context).getViewCache(); } public void setFolder(Folder folder) { @@ -111,57 +110,13 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { } /** - * 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}. - */ - public static void calculateGridSize(int count, int countX, int countY, int maxCountX, - int maxCountY, int maxItemsPerPage, int[] out) { - boolean done; - int gridCountX = countX; - int gridCountY = countY; - - if (count >= maxItemsPerPage) { - gridCountX = maxCountX; - gridCountY = maxCountY; - done = true; - } else { - done = false; - } - - while (!done) { - int oldCountX = gridCountX; - int oldCountY = gridCountY; - if (gridCountX * gridCountY < count) { - // Current grid is too small, expand it - if ((gridCountX <= gridCountY || gridCountY == maxCountY) - && gridCountX < maxCountX) { - gridCountX++; - } else if (gridCountY < maxCountY) { - gridCountY++; - } - 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 = 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) { + private void setupContentDimensions(int count) { mAllocatedContentSize = count; - calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage, - sTmpArray); - mGridCountX = sTmpArray[0]; - mGridCountY = sTmpArray[1]; + mOrganizer.setContentSize(count); + mGridCountX = mOrganizer.getCountX(); + mGridCountY = mOrganizer.getCountY(); // Update grid size for (int i = getPageCount() - 1; i >= 0; i--) { @@ -178,35 +133,50 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { /** * Binds items to the layout. */ - public void bindItems(ArrayList<WorkspaceItemInfo> items) { - ArrayList<View> icons = new ArrayList<>(); - for (WorkspaceItemInfo item : items) { - icons.add(createNewView(item)); + public void bindItems(List<WorkspaceItemInfo> items) { + if (mViewsBound) { + unbindItems(); } - arrangeChildren(icons, icons.size(), false); + arrangeChildren(items.stream().map(this::createNewView).collect(Collectors.toList())); + mViewsBound = true; } - public void allocateSpaceForRank(int rank) { - ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder()); - views.add(rank, null); - arrangeChildren(views, views.size(), false); + /** + * Removes all the icons from the folder + */ + public void unbindItems() { + for (int i = getChildCount() - 1; i >= 0; i--) { + CellLayout page = (CellLayout) getChildAt(i); + ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets(); + for (int j = container.getChildCount() - 1; j >= 0; j--) { + mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j)); + } + page.removeAllViews(); + mViewCache.recycleView(R.layout.folder_page, page); + } + removeAllViews(); + mViewsBound = false; } /** - * Create space for a new item at the end, and returns the rank for that item. - * Also sets the current page to the last page. + * Returns true if the icons are bound to the folder */ - public int allocateRankForNewItem() { - int rank = getItemCount(); - allocateSpaceForRank(rank); - setCurrentPage(rank / mMaxItemsPerPage); - return rank; + public boolean areViewsBound() { + return mViewsBound; } + /** + * Creates and adds an icon corresponding to the provided rank + * @return the created icon + */ public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) { View icon = createNewView(item); - allocateSpaceForRank(rank); - addViewForRank(icon, item, rank); + if (!mViewsBound) { + return icon; + } + ArrayList<View> views = new ArrayList<>(mFolder.getIconsInReadingOrder()); + views.add(rank, icon); + arrangeChildren(views); return icon; } @@ -215,31 +185,33 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { * related attributes. It assumes that {@param item} is already attached to the view. */ public void addViewForRank(View view, WorkspaceItemInfo item, int rank) { - int pagePos = rank % mMaxItemsPerPage; - int pageNo = rank / mMaxItemsPerPage; - - item.rank = rank; - item.cellX = pagePos % mGridCountX; - item.cellY = pagePos / mGridCountX; + int pageNo = rank / mOrganizer.getMaxItemsPerPage(); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); - lp.cellX = item.cellX; - lp.cellY = item.cellY; + lp.setXY(mOrganizer.getPosForRank(rank)); getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true); } @SuppressLint("InflateParams") public View createNewView(WorkspaceItemInfo item) { - final BubbleTextView textView = (BubbleTextView) mInflater.inflate( - R.layout.folder_application, null, false); + if (item == null) { + return null; + } + final BubbleTextView textView = mViewCache.getView( + R.layout.folder_application, getContext(), null); textView.applyFromWorkspaceItem(item); - textView.setHapticFeedbackEnabled(false); textView.setOnClickListener(ItemClickHandler.INSTANCE); textView.setOnLongClickListener(mFolder); textView.setOnFocusChangeListener(mFocusIndicatorHelper); - - textView.setLayoutParams(new CellLayout.LayoutParams( - item.cellX, item.cellY, item.spanX, item.spanY)); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) textView.getLayoutParams(); + if (lp == null) { + textView.setLayoutParams(new CellLayout.LayoutParams( + item.cellX, item.cellY, item.spanX, item.spanY)); + } else { + lp.cellX = item.cellX; + lp.cellY = item.cellY; + lp.cellHSpan = lp.cellVSpan = 1; + } return textView; } @@ -254,7 +226,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { private CellLayout createAndAddNewPage() { DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false); + CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this); page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx); page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); page.setInvertIfRtl(true); @@ -295,37 +267,28 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { * page. * * @param list the ordered list of children. - * @param itemCount if greater than the total children count, empty spaces are left - * at the end, otherwise it is ignored. - * */ - public void arrangeChildren(ArrayList<View> list, int itemCount) { - arrangeChildren(list, itemCount, true); - } - @SuppressLint("RtlHardcoded") - private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) { + public void arrangeChildren(List<View> list) { + int itemCount = list.size(); ArrayList<CellLayout> pages = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { CellLayout page = (CellLayout) getChildAt(i); page.removeAllViews(); pages.add(page); } + mOrganizer.setFolderInfo(mFolder.getInfo()); setupContentDimensions(itemCount); Iterator<CellLayout> pageItr = pages.iterator(); CellLayout currentPage = null; int position = 0; - int newX, newY, rank; + int rank = 0; - FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier( - Launcher.getLauncher(getContext()).getDeviceProfile().inv); - verifier.setFolderInfo(mFolder.getInfo()); - rank = 0; for (int i = 0; i < itemCount; i++) { View v = list.size() > i ? list.get(i) : null; - if (currentPage == null || position >= mMaxItemsPerPage) { + if (currentPage == null || position >= mOrganizer.getMaxItemsPerPage()) { // Next page if (pageItr.hasNext()) { currentPage = pageItr.next(); @@ -337,28 +300,16 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { if (v != null) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); - newX = position % mGridCountX; - newY = position / mGridCountX; ItemInfo info = (ItemInfo) v.getTag(); - if (info.cellX != newX || info.cellY != newY || info.rank != rank) { - info.cellX = newX; - info.cellY = newY; - info.rank = rank; - if (saveChanges) { - mFolder.mLauncher.getModelWriter().addOrMoveItemInDatabase(info, - mFolder.mInfo.id, 0, info.cellX, info.cellY); - } - } - lp.cellX = info.cellX; - lp.cellY = info.cellY; + lp.setXY(mOrganizer.getPosForRank(rank)); currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true); - if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) { + if (mOrganizer.isItemInPreview(rank) && v instanceof BubbleTextView) { ((BubbleTextView) v).verifyHighRes(); } } - rank ++; + rank++; position++; } @@ -391,16 +342,6 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0; } - public int getItemCount() { - int lastPageIndex = getChildCount() - 1; - if (lastPageIndex < 0) { - // If there are no pages, nothing has yet been added to the folder. - return 0; - } - return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount() - + lastPageIndex * mMaxItemsPerPage; - } - /** * @return the rank of the cell nearest to the provided pixel position. */ @@ -412,31 +353,28 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1; } return Math.min(mAllocatedContentSize - 1, - pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]); + pageIndex * mOrganizer.getMaxItemsPerPage() + + sTmpArray[1] * mGridCountX + sTmpArray[0]); } public View getFirstItem() { - if (getChildCount() < 1) { - return null; - } - ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets(); - if (mGridCountX > 0) { - return currContainer.getChildAt(0, 0); - } else { - return currContainer.getChildAt(0); - } + return getViewInCurrentPage(c -> 0); } public View getLastItem() { + return getViewInCurrentPage(c -> c.getChildCount() - 1); + } + + private View getViewInCurrentPage(ToIntFunction<ShortcutAndWidgetContainer> rankProvider) { if (getChildCount() < 1) { return null; } - ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets(); - int lastRank = currContainer.getChildCount() - 1; + ShortcutAndWidgetContainer container = getCurrentCellLayout().getShortcutsAndWidgets(); + int rank = rankProvider.applyAsInt(container); if (mGridCountX > 0) { - return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX); + return container.getChildAt(rank % mGridCountX, rank / mGridCountX); } else { - return currContainer.getChildAt(lastRank); + return container.getChildAt(rank); } } @@ -517,7 +455,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { } public boolean rankOnCurrentPage(int rank) { - int p = rank / mMaxItemsPerPage; + int p = rank / mOrganizer.getMaxItemsPerPage(); return p == getNextPage(); } @@ -563,15 +501,16 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { // Animation only happens on the current page. int pageToAnimate = getNextPage(); + int maxItemsPerPage = mOrganizer.getMaxItemsPerPage(); - int pageT = target / mMaxItemsPerPage; - int pagePosT = target % mMaxItemsPerPage; + int pageT = target / maxItemsPerPage; + int pagePosT = target % maxItemsPerPage; if (pageT != pageToAnimate) { Log.e(TAG, "Cannot animate when the target cell is invisible"); } - int pagePosE = empty % mMaxItemsPerPage; - int pageE = empty / mMaxItemsPerPage; + int pagePosE = empty % maxItemsPerPage; + int pageE = empty / maxItemsPerPage; int startPos, endPos; int moveStart, moveEnd; @@ -588,7 +527,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { if (pageE < pageToAnimate) { moveStart = empty; // Instantly move the first item in the current page. - moveEnd = pageToAnimate * mMaxItemsPerPage; + moveEnd = pageToAnimate * maxItemsPerPage; // Animate the 2nd item in the current page, as the first item was already moved to // the last page. startPos = 0; @@ -606,10 +545,10 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { // Move the items immediately. moveStart = empty; // Instantly move the last item in the current page. - moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1; + moveEnd = (pageToAnimate + 1) * maxItemsPerPage - 1; // Animations start with the second last item in the page - startPos = mMaxItemsPerPage - 1; + startPos = maxItemsPerPage - 1; } else { moveStart = moveEnd = -1; startPos = pagePosE; @@ -621,8 +560,8 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { // Instant moving views. while (moveStart != moveEnd) { int rankToMove = moveStart + direction; - int p = rankToMove / mMaxItemsPerPage; - int pagePos = rankToMove % mMaxItemsPerPage; + int p = rankToMove / maxItemsPerPage; + int pagePos = rankToMove % maxItemsPerPage; int x = pagePos % mGridCountX; int y = pagePos / mGridCountX; @@ -667,9 +606,6 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { for (int i = startPos; i != endPos; i += direction) { int nextPos = i + direction; View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX); - if (v != null) { - ((ItemInfo) v.getTag()).rank -= direction; - } if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX, REORDER_ANIMATION_DURATION, delay, true, true)) { delay += delayAmount; @@ -679,6 +615,6 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { } public int itemsPerPage() { - return mMaxItemsPerPage; + return mOrganizer.getMaxItemsPerPage(); } } diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java index c81846256..caf6e55b7 100644 --- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java +++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java @@ -17,6 +17,8 @@ package com.android.launcher3.folder; import android.graphics.drawable.Drawable; +import com.android.launcher3.WorkspaceItemInfo; + /** * Manages the parameters used to draw a Folder preview item. */ @@ -25,9 +27,10 @@ class PreviewItemDrawingParams { float transY; float scale; float overlayAlpha; - FolderPreviewItemAnim anim; + public FolderPreviewItemAnim anim; public boolean hidden; - Drawable drawable; + public Drawable drawable; + public WorkspaceItemInfo item; PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) { this.transX = transX; diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 49763ba9d..2d817e6d2 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -23,28 +23,51 @@ import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; +import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.util.FloatProperty; import android.view.View; import android.widget.TextView; -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.WorkspaceItemInfo; +import androidx.annotation.NonNull; + +import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.graphics.DrawableFactory; +import com.android.launcher3.graphics.PreloadIconDrawable; import java.util.ArrayList; import java.util.List; - -import androidx.annotation.NonNull; +import java.util.function.Predicate; /** * Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}. */ public class PreviewItemManager { - private FolderIcon mIcon; + private static final FloatProperty<PreviewItemManager> CURRENT_PAGE_ITEMS_TRANS_X = + new FloatProperty<PreviewItemManager>("currentPageItemsTransX") { + @Override + public void setValue(PreviewItemManager manager, float v) { + manager.mCurrentPageItemsTransX = v; + manager.onParamsChanged(); + } + + @Override + public Float get(PreviewItemManager manager) { + return manager.mCurrentPageItemsTransX; + } + }; + + private final Context mContext; + private final FolderIcon mIcon; + private final DrawableFactory mDrawableFactory; + private final int mIconSize; // These variables are all associated with the drawing of the preview; they are stored // as member variables for shared usage and to avoid computation on each frame @@ -69,7 +92,10 @@ public class PreviewItemManager { private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200; public PreviewItemManager(FolderIcon icon) { + mContext = icon.getContext(); mIcon = icon; + mDrawableFactory = DrawableFactory.INSTANCE.get(mContext); + mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx; } /** @@ -200,7 +226,7 @@ public class PreviewItemManager { } void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) { - List<BubbleTextView> items = mIcon.getPreviewItemsOnPage(page); + List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page); int prevNumItems = params.size(); // We adjust the size of the list to match the number of items in the preview. @@ -214,13 +240,7 @@ public class PreviewItemManager { int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW; for (int i = 0; i < params.size(); i++) { PreviewItemDrawingParams p = params.get(i); - p.drawable = items.get(i).getCompoundDrawables()[1]; - - if (p.drawable != null && !mIcon.mFolder.isOpen()) { - // Set the callback to FolderIcon as it is responsible to drawing the icon. The - // callback will be released when the folder is opened. - p.drawable.setCallback(mIcon); - } + setDrawable(p, items.get(i)); if (!animate) { computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p); @@ -253,14 +273,8 @@ public class PreviewItemManager { buildParamsForPage(currentPage, mCurrentPageParams, false); onParamsChanged(); - ValueAnimator slideAnimator = ValueAnimator.ofFloat(0, ITEM_SLIDE_IN_OUT_DISTANCE_PX); - slideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mCurrentPageItemsTransX = (float) valueAnimator.getAnimatedValue(); - onParamsChanged(); - } - }); + ValueAnimator slideAnimator = ObjectAnimator + .ofFloat(this, CURRENT_PAGE_ITEMS_TRANS_X, 0, ITEM_SLIDE_IN_OUT_DISTANCE_PX); slideAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -277,6 +291,25 @@ public class PreviewItemManager { buildParamsForPage(0, mFirstPageParams, animate); } + void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) { + boolean modified = false; + for (PreviewItemDrawingParams param : mFirstPageParams) { + if (itemCheck.test(param.item)) { + setDrawable(param, param.item); + modified = true; + } + } + for (PreviewItemDrawingParams param : mCurrentPageParams) { + if (itemCheck.test(param.item)) { + setDrawable(param, param.item); + modified = true; + } + } + if (modified) { + mIcon.invalidate(); + } + } + boolean verifyDrawable(@NonNull Drawable who) { for (int i = 0; i < mFirstPageParams.size(); i++) { if (mFirstPageParams.get(i).drawable == who) { @@ -296,46 +329,46 @@ public class PreviewItemManager { * - Moving into a new position * - Moving out of the preview * - * @param oldParams The list of items in the old preview. - * @param newParams The list of items in the new preview. + * @param oldItems The list of items in the old preview. + * @param newItems The list of items in the new preview. * @param dropped The item that was dropped onto the FolderIcon. */ - public void onDrop(List<BubbleTextView> oldParams, List<BubbleTextView> newParams, + public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems, WorkspaceItemInfo dropped) { - int numItems = newParams.size(); + int numItems = newItems.size(); final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams; buildParamsForPage(0, params, false); // New preview items for items that are moving in (except for the dropped item). - List<BubbleTextView> moveIn = new ArrayList<>(); - for (BubbleTextView btv : newParams) { - if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) { - moveIn.add(btv); + List<WorkspaceItemInfo> moveIn = new ArrayList<>(); + for (WorkspaceItemInfo newItem : newItems) { + if (!oldItems.contains(newItem) && !newItem.equals(dropped)) { + moveIn.add(newItem); } } for (int i = 0; i < moveIn.size(); ++i) { - int prevIndex = newParams.indexOf(moveIn.get(i)); + int prevIndex = newItems.indexOf(moveIn.get(i)); PreviewItemDrawingParams p = params.get(prevIndex); computePreviewItemDrawingParams(prevIndex, numItems, p); - updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)), + updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newItems.indexOf(moveIn.get(i)), numItems); } // Items that are moving into new positions within the preview. - for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) { - int oldIndex = oldParams.indexOf(newParams.get(newIndex)); + for (int newIndex = 0; newIndex < newItems.size(); ++newIndex) { + int oldIndex = oldItems.indexOf(newItems.get(newIndex)); if (oldIndex >= 0 && newIndex != oldIndex) { PreviewItemDrawingParams p = params.get(newIndex); - updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems); + updateTransitionParam(p, newItems.get(newIndex), oldIndex, newIndex, numItems); } } // Old preview items that need to be moved out. - List<BubbleTextView> moveOut = new ArrayList<>(oldParams); - moveOut.removeAll(newParams); + List<WorkspaceItemInfo> moveOut = new ArrayList<>(oldItems); + moveOut.removeAll(newItems); for (int i = 0; i < moveOut.size(); ++i) { - BubbleTextView item = moveOut.get(i); - int oldIndex = oldParams.indexOf(item); + WorkspaceItemInfo item = moveOut.get(i); + int oldIndex = oldItems.indexOf(item); PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null); updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems); params.add(0, p); // We want these items first so that they are on drawn last. @@ -348,14 +381,9 @@ public class PreviewItemManager { } } - private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv, + private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item, int prevIndex, int newIndex, int numItems) { - p.drawable = btv.getCompoundDrawables()[1]; - if (!mIcon.mFolder.isOpen()) { - // Set the callback to FolderIcon as it is responsible to drawing the icon. The - // callback will be released when the folder is opened. - p.drawable.setCallback(mIcon); - } + setDrawable(p, item); FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems, newIndex, numItems, DROP_IN_ANIMATION_DURATION, null); @@ -364,4 +392,20 @@ public class PreviewItemManager { } p.anim = anim; } + + private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) { + if (item.hasPromiseIconUi()) { + PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item); + drawable.setLevel(item.getInstallProgress()); + p.drawable = drawable; + } else { + p.drawable = mDrawableFactory.newIcon(mContext, item); + } + p.drawable.setBounds(0, 0, mIconSize, mIconSize); + p.item = item; + + // Set the callback to FolderIcon as it is responsible to drawing the icon. The + // callback will be released when the folder is opened. + p.drawable.setCallback(mIcon); + } } diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index 9263a2ac9..f57945197 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -16,6 +16,8 @@ package com.android.launcher3.graphics; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; @@ -25,7 +27,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Handler; import android.view.View; import com.android.launcher3.BubbleTextView; @@ -35,7 +36,6 @@ import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.BitmapRenderer; -import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.PendingAppWidgetHostView; @@ -157,7 +157,7 @@ public class DragPreviewProvider { } mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview); - new Handler(UiThreadHelper.getBackgroundLooper()).post(mOutlineGeneratorCallback); + UI_HELPER_EXECUTOR.post(mOutlineGeneratorCallback); } protected static Rect getDrawableBounds(Drawable d) { @@ -195,15 +195,22 @@ public class DragPreviewProvider { private final Bitmap mPreviewSnapshot; private final Context mContext; + private final boolean mIsIcon; OutlineGeneratorCallback(Bitmap preview) { mPreviewSnapshot = preview; mContext = mView.getContext(); + mIsIcon = mView instanceof BubbleTextView; } @Override public void run() { Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot); + if (mIsIcon) { + int size = Launcher.getLauncher(mContext).getDeviceProfile().iconSizePx; + preview = Bitmap.createScaledBitmap(preview, size, size, false); + } + //else case covers AppWidgetHost (doesn't drag/drop across different device profiles) // We start by removing most of the alpha channel so as to ignore shadows, and // other types of partial transparency when defining the shape of the object diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java index 288749fa7..837301fb8 100644 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ b/src/com/android/launcher3/graphics/DrawableFactory.java @@ -80,26 +80,25 @@ public class DrawableFactory implements ResourceBasedOverride { * Returns a drawable that can be used as a badge for the user or null. */ @UiThread - public Drawable getBadgeForUser(UserHandle user, Context context) { + public Drawable getBadgeForUser(UserHandle user, Context context, int badgeSize) { if (mMyUser.equals(user)) { return null; } - Bitmap badgeBitmap = getUserBadge(user, context); + Bitmap badgeBitmap = getUserBadge(user, context, badgeSize); FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap); d.setFilterBitmap(true); d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight()); return d; } - protected synchronized Bitmap getUserBadge(UserHandle user, Context context) { + protected synchronized Bitmap getUserBadge(UserHandle user, Context context, int badgeSize) { Bitmap badgeBitmap = mUserBadges.get(user); if (badgeBitmap != null) { return badgeBitmap; } final Resources res = context.getApplicationContext().getResources(); - int badgeSize = res.getDimensionPixelSize(R.dimen.profile_badge_size); badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity( diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java index efd39ee8e..71b436600 100644 --- a/src/com/android/launcher3/graphics/GridOptionsProvider.java +++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java @@ -1,5 +1,7 @@ package com.android.launcher3.graphics; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.ContentProvider; import android.content.ContentValues; import android.content.res.XmlResourceParser; @@ -17,8 +19,6 @@ import android.util.Xml; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.InvariantDeviceProfile.GridOption; import com.android.launcher3.R; -import com.android.launcher3.util.LooperExecutor; -import com.android.launcher3.util.UiThreadHelper; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -180,10 +180,10 @@ public class GridOptionsProvider extends ContentProvider { throw new FileNotFoundException(e.getMessage()); } - LooperExecutor executor = new LooperExecutor(UiThreadHelper.getBackgroundLooper()); try { return openPipeHelper(uri, MIME_TYPE_PNG, null, - executor.submit(new LauncherPreviewRenderer(getContext(), idp)), BITMAP_WRITER); + UI_HELPER_EXECUTOR.submit(new LauncherPreviewRenderer(getContext(), idp)), + BITMAP_WRITER); } catch (Exception e) { throw new FileNotFoundException(e.getMessage()); } diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java index 46b500265..832956d7e 100644 --- a/src/com/android/launcher3/icons/ComponentWithLabel.java +++ b/src/com/android/launcher3/icons/ComponentWithLabel.java @@ -34,9 +34,11 @@ public interface ComponentWithLabel { class ComponentCachingLogic implements CachingLogic<ComponentWithLabel> { private final PackageManager mPackageManager; + private final boolean mAddToMemCache; - public ComponentCachingLogic(Context context) { + public ComponentCachingLogic(Context context, boolean addToMemCache) { mPackageManager = context.getPackageManager(); + mAddToMemCache = addToMemCache; } @Override @@ -60,5 +62,10 @@ public interface ComponentWithLabel { // Do not load icon. target.icon = BitmapInfo.LOW_RES_ICON; } + + @Override + public boolean addToMemCache() { + return mAddToMemCache; + } } } diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index abff237e8..9c462606a 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -16,6 +16,9 @@ package com.android.launcher3.icons; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -36,8 +39,6 @@ import com.android.launcher3.IconProvider; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherFiles; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.LauncherAppsCompat; @@ -60,8 +61,6 @@ public class IconCache extends BaseIconCache { private static final String TAG = "Launcher.IconCache"; - private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); - private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic; private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic; @@ -73,9 +72,9 @@ public class IconCache extends BaseIconCache { private int mPendingIconRequestCount = 0; public IconCache(Context context, InvariantDeviceProfile inv) { - super(context, LauncherFiles.APP_ICONS_DB, LauncherModel.getWorkerLooper(), + super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(), inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */); - mComponentWithLabelCachingLogic = new ComponentCachingLogic(context); + mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false); mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context); mLauncherApps = LauncherAppsCompat.getInstance(mContext); mUserManager = UserManagerCompat.getInstance(mContext); @@ -124,7 +123,7 @@ public class IconCache extends BaseIconCache { final ItemInfoWithIcon info) { Preconditions.assertUIThread(); if (mPendingIconRequestCount <= 0) { - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_FOREGROUND); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); } mPendingIconRequestCount ++; @@ -136,7 +135,7 @@ public class IconCache extends BaseIconCache { } else if (info instanceof PackageItemInfo) { getTitleAndIconForApp((PackageItemInfo) info, false); } - mMainThreadExecutor.execute(() -> { + MAIN_EXECUTOR.execute(() -> { caller.reapplyItemInfo(info); onEnd(); }); @@ -149,7 +148,7 @@ public class IconCache extends BaseIconCache { private void onIconRequestEnd() { mPendingIconRequestCount --; if (mPendingIconRequestCount <= 0) { - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } } @@ -195,7 +194,7 @@ public class IconCache extends BaseIconCache { public synchronized String getTitleNoCache(ComponentWithLabel info) { CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info, mComponentWithLabelCachingLogic, false /* usePackageIcon */, - true /* useLowResIcon */, false /* addToMemCache */); + true /* useLowResIcon */); return Utilities.trim(entry.title); } diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java index 763240885..adc92c46c 100644 --- a/src/com/android/launcher3/icons/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -24,6 +24,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.os.Process; +import androidx.annotation.Nullable; + import com.android.launcher3.AppInfo; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.InvariantDeviceProfile; @@ -37,8 +39,6 @@ import com.android.launcher3.util.Themes; import java.util.function.Supplier; -import androidx.annotation.Nullable; - /** * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class * that are threadsafe. diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java index f20f3659e..3ecfb23c2 100644 --- a/src/com/android/launcher3/logging/EventLogArray.java +++ b/src/com/android/launcher3/logging/EventLogArray.java @@ -16,11 +16,13 @@ package com.android.launcher3.logging; +import android.util.Log; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Locale; +import java.util.Random; /** * A utility class to record and log events. Events are stored in a fixed size array and old logs @@ -37,6 +39,7 @@ public class EventLogArray { private final String name; private final EventEntry[] logs; private int nextIndex; + private int mLogId; public EventLogArray(String name, int size) { this.name = name; @@ -52,10 +55,6 @@ public class EventLogArray { addLog(TYPE_INTEGER, event, extras); } - public void addLog(String event, float extras) { - addLog(TYPE_FLOAT, event, extras); - } - public void addLog(String event, boolean extras) { addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0); } @@ -65,7 +64,7 @@ public class EventLogArray { int last = (nextIndex + logs.length - 1) % logs.length; int secondLast = (nextIndex + logs.length - 2) % logs.length; if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) { - logs[last].update(type, event, extras); + logs[last].update(type, event, extras, mLogId); logs[secondLast].duplicateCount++; return; } @@ -73,7 +72,7 @@ public class EventLogArray { if (logs[nextIndex] == null) { logs[nextIndex] = new EventEntry(); } - logs[nextIndex].update(type, event, extras); + logs[nextIndex].update(type, event, extras, mLogId); nextIndex = (nextIndex + 1) % logs.length; } @@ -113,10 +112,18 @@ public class EventLogArray { if (log.duplicateCount > 0) { msg.append(" & ").append(log.duplicateCount).append(" similar events"); } + msg.append(" traceId: ").append(log.traceId); writer.println(msg); } } + /** Returns a 3 digit random number between 100-999 */ + public int generateAndSetLogId() { + Random r = new Random(); + mLogId = r.nextInt(900) + 100; + return mLogId; + } + private boolean isEntrySame(EventEntry entry, int type, String event) { return entry != null && entry.type == type && entry.event.equals(event); } @@ -129,11 +136,13 @@ public class EventLogArray { private float extras; private long time; private int duplicateCount; + private int traceId; - public void update(int type, String event, float extras) { + public void update(int type, String event, float extras, int traceId) { this.type = type; this.event = event; this.extras = extras; + this.traceId = traceId; time = System.currentTimeMillis(); duplicateCount = 0; } diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java index f7f8ef18f..923a89b1c 100644 --- a/src/com/android/launcher3/logging/FileLog.java +++ b/src/com/android/launcher3/logging/FileLog.java @@ -1,13 +1,14 @@ package com.android.launcher3.logging; +import static com.android.launcher3.util.Executors.createAndStartNewLooper; + import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.util.Log; import android.util.Pair; -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.IOUtils; import java.io.BufferedReader; import java.io.File; @@ -29,8 +30,7 @@ import java.util.concurrent.TimeUnit; */ public final class FileLog { - protected static final boolean ENABLED = - FeatureFlags.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE; + protected static final boolean ENABLED = true; private static final String FILE_NAME_PREFIX = "log-"; private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); @@ -91,9 +91,8 @@ public final class FileLog { private static Handler getHandler() { synchronized (DATE_FORMAT) { if (sHandler == null) { - HandlerThread thread = new HandlerThread("file-logger"); - thread.start(); - sHandler = new Handler(thread.getLooper(), new LogWriterCallback()); + sHandler = new Handler(createAndStartNewLooper("file-logger"), + new LogWriterCallback()); } } return sHandler; @@ -131,7 +130,7 @@ public final class FileLog { private PrintWriter mCurrentWriter = null; private void closeWriter() { - Utilities.closeSilently(mCurrentWriter); + IOUtils.closeSilently(mCurrentWriter); mCurrentWriter = null; } @@ -219,7 +218,7 @@ public final class FileLog { } catch (Exception e) { // ignore } finally { - Utilities.closeSilently(in); + IOUtils.closeSilently(in); } } } diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java index 9b75b43b4..598792abc 100644 --- a/src/com/android/launcher3/logging/LoggerUtils.java +++ b/src/com/android/launcher3/logging/LoggerUtils.java @@ -44,6 +44,7 @@ import java.lang.reflect.Modifier; public class LoggerUtils { private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>(); private static final String UNKNOWN = "UNKNOWN"; + private static final int DEFAULT_PREDICTED_RANK = -100; public static String getFieldName(int value, Class c) { SparseArray<String> cache; @@ -90,7 +91,7 @@ public class LoggerUtils { } public static String getTargetStr(Target t) { - if (t == null){ + if (t == null) { return ""; } String str = ""; @@ -137,17 +138,16 @@ public class LoggerUtils { if (t.intentHash != 0) { typeStr += ", intentHash=" + t.intentHash; } - if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) && - t.itemType != ItemType.TASK) { + if (t.itemType == ItemType.FOLDER_ICON) { + typeStr += ", grid(" + t.gridX + "," + t.gridY + ")"; + } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) + && t.itemType != ItemType.TASK) { typeStr += ", predictiveRank=" + t.predictedRank + ", grid(" + t.gridX + "," + t.gridY - + "), span(" + t.spanX + "," + t.spanY - + "), pageIdx=" + t.pageIndex; - + + "), span(" + t.spanX + "," + t.spanY + "), pageIdx=" + t.pageIndex; } if (t.searchQueryLength != 0) { typeStr += ", searchQueryLength=" + t.searchQueryLength; } - if (t.itemType == ItemType.TASK) { typeStr += ", pageIdx=" + t.pageIndex; } @@ -168,17 +168,17 @@ public class LoggerUtils { public static Target newItemTarget(ItemInfo info, InstantAppResolver instantAppResolver) { Target t = newTarget(Target.Type.ITEM); - switch (info.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: t.itemType = (instantAppResolver != null && info instanceof AppInfo - && instantAppResolver.isInstantApp(((AppInfo) info)) ) + && instantAppResolver.isInstantApp(((AppInfo) info))) ? ItemType.WEB_APP : ItemType.APP_ICON; - t.predictedRank = -100; // Never assigned + t.predictedRank = DEFAULT_PREDICTED_RANK; break; case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: t.itemType = ItemType.SHORTCUT; + t.predictedRank = DEFAULT_PREDICTED_RANK; break; case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: t.itemType = ItemType.FOLDER_ICON; @@ -188,6 +188,7 @@ public class LoggerUtils { break; case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: t.itemType = ItemType.DEEPSHORTCUT; + t.predictedRank = DEFAULT_PREDICTED_RANK; break; } return t; diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java index c72b07a7f..99906fe1a 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -26,6 +26,8 @@ import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; import static com.android.launcher3.logging.LoggerUtils.newTarget; import static com.android.launcher3.logging.LoggerUtils.newTouchAction; +import static java.util.Optional.ofNullable; + import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -35,6 +37,7 @@ import android.os.SystemClock; import android.util.Log; import android.view.View; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.DropTarget; @@ -58,7 +61,7 @@ import java.util.UUID; /** * Manages the creation of {@link LauncherEvent}. * To debug this class, execute following command before side loading a new apk. - * + * <p> * $ adb shell setprop log.tag.UserEvent VERBOSE */ public class UserEventDispatcher implements ResourceBasedOverride { @@ -94,19 +97,26 @@ public class UserEventDispatcher implements ResourceBasedOverride { /** * Fills in the container data on the given event if the given view is not null. + * * @return whether container data was added. */ - public static boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) { + public boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) { // Fill in grid(x,y), pageIndex of the child and container type of the parent LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v); if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) { return false; } - ItemInfo itemInfo = (ItemInfo) v.getTag(); - provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]); + final ItemInfo itemInfo = (ItemInfo) v.getTag(); + final Target target = event.srcTarget[0]; + final Target targetParent = event.srcTarget[1]; + onFillInLogContainerData(itemInfo, target, targetParent); + provider.fillInLogContainerData(v, itemInfo, target, targetParent); return true; } + protected void onFillInLogContainerData( + @NonNull ItemInfo itemInfo, @NonNull Target target, @NonNull Target targetParent) { } + private boolean mSessionStarted; private long mElapsedContainerMillis; private long mElapsedSessionMillis; @@ -139,7 +149,11 @@ public class UserEventDispatcher implements ResourceBasedOverride { mAppOrTaskLaunch = true; } - public void logActionTip(int actionType, int viewType) { } + /** + * Dummy method. + */ + public void logActionTip(int actionType, int viewType) { + } @Deprecated public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex, @@ -184,15 +198,15 @@ public class UserEventDispatcher implements ResourceBasedOverride { public void logActionCommand(int command, int srcContainerType, int dstContainerType) { logActionCommand(command, newContainerTarget(srcContainerType), - dstContainerType >=0 ? newContainerTarget(dstContainerType) : null); + dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null); } public void logActionCommand(int command, int srcContainerType, int dstContainerType, - int pageIndex) { + int pageIndex) { Target srcTarget = newContainerTarget(srcContainerType); srcTarget.pageIndex = pageIndex; logActionCommand(command, srcTarget, - dstContainerType >=0 ? newContainerTarget(dstContainerType) : null); + dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null); } public void logActionCommand(int command, Target srcTarget, Target dstTarget) { @@ -241,7 +255,7 @@ public class UserEventDispatcher implements ResourceBasedOverride { } public void logActionOnControl(int action, int controlType, int parentContainer, - int grandParentContainer){ + int grandParentContainer) { LauncherEvent event = newLauncherEvent(newTouchAction(action), newControlTarget(controlType), newContainerTarget(parentContainer), @@ -250,11 +264,11 @@ public class UserEventDispatcher implements ResourceBasedOverride { } public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer, - int parentContainerType) { + int parentContainerType) { final LauncherEvent event = (controlInContainer == null && parentContainerType < 0) ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL)) : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL), - newTarget(Target.Type.CONTAINER)); + newTarget(Target.Type.CONTAINER)); event.srcTarget[0].controlType = controlType; if (controlInContainer != null) { fillInLogContainerData(event, controlInContainer); @@ -301,9 +315,9 @@ public class UserEventDispatcher implements ResourceBasedOverride { * (1) WORKSPACE: if the launcher is the foreground activity * (2) APP: if another app was the foreground activity */ - public void logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType, - int srcParentContainerType, int dstContainerType, - int pageIndex) { + public void logStateChangeAction(int action, int dir, int downX, int downY, + int srcChildTargetType, int srcParentContainerType, int dstContainerType, + int pageIndex) { LauncherEvent event; if (srcChildTargetType == LauncherLogProto.ItemType.TASK) { event = newLauncherEvent(newTouchAction(action), @@ -326,9 +340,25 @@ public class UserEventDispatcher implements ResourceBasedOverride { } public void logActionOnItem(int action, int dir, int itemType) { + logActionOnItem(action, dir, itemType, null, null); + } + + /** + * Creates new {@link LauncherEvent} of ITEM target type with input arguments and dispatches it. + * + * @param touchAction ENUM value of {@link LauncherLogProto.Action.Touch} Action + * @param dir ENUM value of {@link LauncherLogProto.Action.Direction} Action + * @param itemType ENUM value of {@link LauncherLogProto.ItemType} + * @param gridX Nullable X coordinate of item's position on the workspace grid + * @param gridY Nullable Y coordinate of item's position on the workspace grid + */ + public void logActionOnItem(int touchAction, int dir, int itemType, + @Nullable Integer gridX, @Nullable Integer gridY) { Target itemTarget = newTarget(Target.Type.ITEM); itemTarget.itemType = itemType; - LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget); + ofNullable(gridX).ifPresent(value -> itemTarget.gridX = value); + ofNullable(gridY).ifPresent(value -> itemTarget.gridY = value); + LauncherEvent event = newLauncherEvent(newTouchAction(touchAction), itemTarget); event.action.dir = dir; dispatchUserEvent(event, null); } @@ -351,7 +381,7 @@ public class UserEventDispatcher implements ResourceBasedOverride { LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP), newItemTarget(dragObj.originalDragInfo, mInstantAppResolver), newTarget(Target.Type.CONTAINER)); - event.destTarget = new Target[] { + event.destTarget = new Target[]{ newItemTarget(dragObj.originalDragInfo, mInstantAppResolver), newDropTarget(dropTargetAsView) }; @@ -373,14 +403,10 @@ public class UserEventDispatcher implements ResourceBasedOverride { int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE; Action action = newCommandAction(actionTouch); action.command = Action.Command.BACK; - action.dir = isButton - ? Action.Direction.NONE - : gestureSwipeLeft - ? Action.Direction.LEFT - : Action.Direction.RIGHT; - Target target = newControlTarget(isButton - ? LauncherLogProto.ControlType.BACK_BUTTON - : LauncherLogProto.ControlType.BACK_GESTURE); + action.dir = isButton ? Action.Direction.NONE : + gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT; + Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON : + LauncherLogProto.ControlType.BACK_GESTURE); target.spanX = downX; target.spanY = downY; target.cardinality = completed ? 1 : 0; @@ -391,6 +417,7 @@ public class UserEventDispatcher implements ResourceBasedOverride { /** * Currently logs following containers: workspace, allapps, widget tray. + * * @param reason */ public final void resetElapsedContainerMillis(String reason) { @@ -427,10 +454,16 @@ public class UserEventDispatcher implements ResourceBasedOverride { mAppOrTaskLaunch = false; ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; - if (!IS_VERBOSE) { return; } + Log.d(TAG, generateLog(ev)); + } + + /** + * Returns a human-readable log for given user event. + */ + public static String generateLog(LauncherEvent ev) { String log = "\n-----------------------------------------------------" + "\naction:" + LoggerUtils.getActionStr(ev.action); if (ev.srcTarget != null && ev.srcTarget.length > 0) { @@ -445,8 +478,7 @@ public class UserEventDispatcher implements ResourceBasedOverride { ev.elapsedSessionMillis, ev.actionDurationMillis); log += "\n\n"; - Log.d(TAG, log); - return; + return log; } private static String getTargetsStr(Target[] targets) { diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 7d4f2f722..b8c583c14 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -22,7 +22,6 @@ import android.os.UserHandle; import android.util.LongSparseArray; import android.util.Pair; -import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; import com.android.launcher3.InvariantDeviceProfile; @@ -30,14 +29,14 @@ 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.model.BgDataModel.Callbacks; import com.android.launcher3.LauncherSettings; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.PackageManagerHelper; import java.util.ArrayList; import java.util.List; @@ -77,6 +76,11 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { if (shortcutExists(dataModel, item.getIntent(), item.user)) { continue; } + + // b/139663018 Short-circuit this logic if the icon is a system app + if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) { + continue; + } } if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { @@ -118,25 +122,39 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { } SessionInfo sessionInfo = packageInstaller.getActiveSessionInfo(item.user, packageName); + List<LauncherActivityInfo> activities = launcherApps + .getActivityList(packageName, item.user); + boolean hasActivity = activities != null && !activities.isEmpty(); + if (sessionInfo == null) { - List<LauncherActivityInfo> activities = launcherApps - .getActivityList(packageName, item.user); - if (activities != null && !activities.isEmpty()) { - // App was installed while launcher was in the background. - itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user) - .makeWorkspaceItem(); - WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo; - wii.title = ""; - wii.applyFrom(app.getIconCache().getDefaultIcon(item.user)); - app.getIconCache().getTitleAndIcon(wii, - ((WorkspaceItemInfo) itemInfo).usingLowResIcon()); - } else { + if (!hasActivity) { // Session was cancelled, do not add. continue; } } else { workspaceInfo.setInstallProgress((int) sessionInfo.getProgress()); } + + if (hasActivity) { + // App was installed while launcher was in the background, + // or app was already installed for another user. + itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user) + .makeWorkspaceItem(); + + if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) { + // We need this additional check here since we treat all auto added + // workspace items as promise icons. At this point we now have the + // correct intent to compare against existing workspace icons. + // Icon already exists on the workspace and should not be auto-added. + continue; + } + + WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo; + wii.title = ""; + wii.applyFrom(app.getIconCache().getDefaultIcon(item.user)); + app.getIconCache().getTitleAndIcon(wii, + ((WorkspaceItemInfo) itemInfo).usingLowResIcon()); + } } // Add the shortcut to the db @@ -200,7 +218,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { intentWithoutPkg = intent.toUri(0); } - boolean isLauncherAppTarget = Utilities.isLauncherAppTarget(intent); + boolean isLauncherAppTarget = PackageManagerHelper.isLauncherAppTarget(intent); synchronized (dataModel) { for (ItemInfo item : dataModel.itemsIdMap) { if (item instanceof WorkspaceItemInfo) { diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java index 8b49c0638..3873a17e0 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/model/AllAppsList.java @@ -14,25 +14,37 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.model; + +import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR; +import static com.android.launcher3.AppInfo.EMPTY_ARRAY; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.os.LocaleList; import android.os.Process; import android.os.UserHandle; import android.util.Log; +import com.android.launcher3.AppFilter; +import com.android.launcher3.AppInfo; +import com.android.launcher3.PromiseAppInfo; +import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.icons.IconCache; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.SafeCloseable; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.function.Consumer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -42,29 +54,40 @@ import androidx.annotation.Nullable; * Stores the list of all applications for the all apps view. */ public class AllAppsList { + private static final String TAG = "AllAppsList"; + private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { }; + public static final int DEFAULT_APPLICATIONS_NUMBER = 42; /** The list off all apps. */ public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER); - /** The list of apps that have been added since the last notify() call. */ - public ArrayList<AppInfo> added = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER); - /** The list of apps that have been removed since the last notify() call. */ - public ArrayList<AppInfo> removed = new ArrayList<>(); - /** The list of apps that have been modified since the last notify() call. */ - public ArrayList<AppInfo> modified = new ArrayList<>(); private IconCache mIconCache; - private AppFilter mAppFilter; + private boolean mDataChanged = false; + private Consumer<AppInfo> mRemoveListener = NO_OP_CONSUMER; + + private AlphabeticIndexCompat mIndex; + /** * Boring constructor. */ public AllAppsList(IconCache iconCache, AppFilter appFilter) { mIconCache = iconCache; mAppFilter = appFilter; + mIndex = new AlphabeticIndexCompat(LocaleList.getDefault()); + } + + /** + * Returns true if there have been any changes since last call. + */ + public boolean getAndResetChangeFlag() { + boolean result = mDataChanged; + mDataChanged = false; + return result; } /** @@ -81,9 +104,10 @@ public class AllAppsList { return; } mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */); + info.sectionName = mIndex.computeSectionName(info.title); data.add(info); - added.add(info); + mDataChanged = true; } public void addPromiseApp(Context context, @@ -94,31 +118,46 @@ public class AllAppsList { if (applicationInfo == null) { PromiseAppInfo info = new PromiseAppInfo(installInfo); mIconCache.getTitleAndIcon(info, info.usingLowResIcon()); + info.sectionName = mIndex.computeSectionName(info.title); + data.add(info); - added.add(info); + mDataChanged = true; } } - 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? - added.clear(); - removed.clear(); - modified.clear(); + public PromiseAppInfo updatePromiseInstallInfo(PackageInstallInfo installInfo) { + UserHandle user = Process.myUserHandle(); + for (int i=0; i < data.size(); i++) { + final AppInfo appInfo = data.get(i); + final ComponentName tgtComp = appInfo.getTargetComponent(); + if (tgtComp != null && tgtComp.getPackageName().equals(installInfo.packageName) + && appInfo.user.equals(user) + && appInfo instanceof PromiseAppInfo) { + final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo; + if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLING) { + promiseAppInfo.level = installInfo.progress; + return promiseAppInfo; + } else if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) { + removeApp(i); + } + } + } + return null; } - public int size() { - return data.size(); + private void removeApp(int index) { + AppInfo removed = data.remove(index); + if (removed != null) { + mDataChanged = true; + mRemoveListener.accept(removed); + } } - public AppInfo get(int index) { - return data.get(index); + public void clear() { + data.clear(); + mDataChanged = false; + // Reset the index as locales might have changed + mIndex = new AlphabeticIndexCompat(LocaleList.getDefault()); } /** @@ -142,8 +181,7 @@ public class AllAppsList { for (int i = data.size() - 1; i >= 0; i--) { AppInfo info = data.get(i); if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) { - removed.add(info); - data.remove(i); + removeApp(i); } } } @@ -157,17 +195,17 @@ public class AllAppsList { AppInfo info = data.get(i); if (matcher.matches(info, info.componentName)) { info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags); - modified.add(info); + mDataChanged = true; } } } - public void updateIconsAndLabels(HashSet<String> packages, UserHandle user, - ArrayList<AppInfo> outUpdates) { + public void updateIconsAndLabels(HashSet<String> packages, UserHandle user) { for (AppInfo info : data) { if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) { mIconCache.updateTitleAndIcon(info); - outUpdates.add(info); + info.sectionName = mIndex.computeSectionName(info.title); + mDataChanged = true; } } } @@ -188,8 +226,7 @@ public class AllAppsList { && packageName.equals(applicationInfo.componentName.getPackageName())) { if (!findActivity(matches, applicationInfo.componentName)) { Log.w(TAG, "Changing shortcut target due to app component name change."); - removed.add(applicationInfo); - data.remove(i); + removeApp(i); } } } @@ -202,7 +239,9 @@ public class AllAppsList { add(new AppInfo(context, info, user), info); } else { mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */); - modified.add(applicationInfo); + applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title); + + mDataChanged = true; } } } else { @@ -211,15 +250,13 @@ public class AllAppsList { final AppInfo applicationInfo = data.get(i); if (user.equals(applicationInfo.user) && packageName.equals(applicationInfo.componentName.getPackageName())) { - removed.add(applicationInfo); mIconCache.remove(applicationInfo.componentName, user); - data.remove(i); + removeApp(i); } } } } - /** * Returns whether <em>apps</em> contains <em>component</em>. */ @@ -247,4 +284,16 @@ public class AllAppsList { } return null; } + + public AppInfo[] copyData() { + AppInfo[] result = data.toArray(EMPTY_ARRAY); + Arrays.sort(result, COMPONENT_KEY_COMPARATOR); + return result; + } + + public SafeCloseable trackRemoves(Consumer<AppInfo> removeListener) { + mRemoveListener = removeListener; + + return () -> mRemoveListener = NO_OP_CONSUMER; + } } diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java index 29a46cfa5..13ab033e4 100644 --- a/src/com/android/launcher3/model/AppLaunchTracker.java +++ b/src/com/android/launcher3/model/AppLaunchTracker.java @@ -51,5 +51,8 @@ public class AppLaunchTracker implements ResourceBasedOverride { public void onStartApp(ComponentName componentName, UserHandle user, @Nullable String container) { } + public void onDismissApp(ComponentName componentName, UserHandle user, + @Nullable String container){} + public void onReturnedToHome() { } } diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 97cf267d3..0a4f00582 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -16,21 +16,21 @@ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.os.Looper; import android.util.Log; -import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.PagedView; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.LooperIdleLock; @@ -65,7 +65,7 @@ public abstract class BaseLoaderResults { public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) { - mUiExecutor = new MainThreadExecutor(); + mUiExecutor = MAIN_EXECUTOR; mApp = app; mBgDataModel = dataModel; mBgAllAppsList = allAppsList; @@ -279,9 +279,8 @@ public abstract class BaseLoaderResults { public void bindAllApps() { // shallow copy - @SuppressWarnings("unchecked") - ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); - executeCallbacksTask(c -> c.bindAllApplications(list), mUiExecutor); + AppInfo[] apps = mBgAllAppsList.copyData(); + executeCallbacksTask(c -> c.bindAllApplications(apps), mUiExecutor); } public abstract void bindWidgets(); diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java index eea3d8c35..e12633bcd 100644 --- a/src/com/android/launcher3/model/BaseModelUpdateTask.java +++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java @@ -17,12 +17,12 @@ package com.android.launcher3.model; import android.util.Log; -import com.android.launcher3.AllAppsList; +import com.android.launcher3.AppInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; @@ -30,6 +30,7 @@ import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.concurrent.Executor; /** @@ -95,12 +96,7 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { public void bindUpdatedWorkspaceItems(final ArrayList<WorkspaceItemInfo> updatedShortcuts) { if (!updatedShortcuts.isEmpty()) { - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindWorkspaceItemsChanged(updatedShortcuts); - } - }); + scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts)); } } @@ -113,23 +109,20 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { public void bindUpdatedWidgets(BgDataModel dataModel) { final ArrayList<WidgetListRowEntry> widgets = dataModel.widgetsModel.getWidgetsList(mApp.getContext()); - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindAllWidgets(widgets); - } - }); + scheduleCallbackTask(c -> c.bindAllWidgets(widgets)); } public void deleteAndBindComponentsRemoved(final ItemInfoMatcher matcher) { getModelWriter().deleteItemsFromDatabase(matcher); // Call the components-removed callback - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindWorkspaceComponentsRemoved(matcher); - } - }); + scheduleCallbackTask(c -> c.bindWorkspaceComponentsRemoved(matcher)); + } + + public void bindApplicationsIfNeeded() { + if (mAllAppsList.getAndResetChangeFlag()) { + AppInfo[] apps = mAllAppsList.copyData(); + scheduleCallbackTask(c -> c.bindAllApplications(apps)); + } } } diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 8f0cd08a9..0e2027050 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -22,11 +22,13 @@ import android.text.TextUtils; import android.util.Log; import android.util.MutableInt; +import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherSettings; +import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; @@ -40,6 +42,10 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.ViewOnDrawExecutor; +import com.android.launcher3.widget.WidgetListRowEntry; + import com.google.protobuf.nano.MessageNano; import java.io.FileDescriptor; @@ -49,6 +55,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -391,4 +398,30 @@ public class BgDataModel { } } } + + public interface Callbacks { + void rebindModel(); + + int getCurrentWorkspaceScreen(); + void clearPendingBinds(); + void startBinding(); + void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons); + void bindScreens(IntArray orderedScreenIds); + void finishFirstPageBind(ViewOnDrawExecutor executor); + void finishBindingItems(int pageBoundFirst); + void preAddApps(); + void bindAppsAdded(IntArray newScreens, + ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated); + void bindPromiseAppProgressUpdated(PromiseAppInfo app); + void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated); + void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); + void bindRestoreItemsChange(HashSet<ItemInfo> updates); + void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); + void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets); + void onPageBoundSynchronously(int page); + void executeOnNextDraw(ViewOnDrawExecutor executor); + void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap); + + void bindAllApplications(AppInfo[] apps); + } } diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java index 7852444de..c1c8be316 100644 --- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java +++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java @@ -18,14 +18,10 @@ package com.android.launcher3.model; import android.content.ComponentName; import android.os.UserHandle; -import com.android.launcher3.AllAppsList; -import com.android.launcher3.AppInfo; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.icons.IconCache; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; import java.util.ArrayList; @@ -53,9 +49,9 @@ public class CacheDataUpdatedTask extends BaseModelUpdateTask { public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { IconCache iconCache = app.getIconCache(); - final ArrayList<AppInfo> updatedApps = new ArrayList<>(); ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>(); + synchronized (dataModel) { for (ItemInfo info : dataModel.itemsIdMap) { if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) { @@ -69,18 +65,10 @@ public class CacheDataUpdatedTask extends BaseModelUpdateTask { } } } - apps.updateIconsAndLabels(mPackages, mUser, updatedApps); + apps.updateIconsAndLabels(mPackages, mUser); } bindUpdatedWorkspaceItems(updatedShortcuts); - - if (!updatedApps.isEmpty()) { - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindAppsAddedOrUpdated(updatedApps); - } - }); - } + bindApplicationsIfNeeded(); } public boolean isValidShortcut(WorkspaceItemInfo si) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 7593a3371..50e1d56ae 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -19,8 +19,9 @@ package com.android.launcher3.model; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED; -import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle; import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.PackageManagerHelper.isSystemApp; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; @@ -32,7 +33,6 @@ import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.ShortcutInfo; -import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; @@ -40,7 +40,6 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.MutableInt; -import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; import com.android.launcher3.InstallShortcutReceiver; @@ -58,7 +57,7 @@ import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIconPreviewVerifier; +import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic; import com.android.launcher3.icons.IconCache; @@ -67,9 +66,11 @@ import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.cache.IconCacheUpdateHandler; import com.android.launcher3.logging.FileLog; import com.android.launcher3.provider.ImportDataTask; +import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.IOUtils; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; @@ -228,9 +229,10 @@ public class LoaderTask implements Runnable { mResults.bindWidgets(); verifyNotStopped(); - TraceHelper.partitionSection(TAG, "step 4.3: Update icon cache"); - updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(mApp.getContext()), - mApp.getModel()::onWidgetLabelsUpdated); + + TraceHelper.partitionSection(TAG, "step 4.3: save widgets in icon cache"); + updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic( + mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 5: Finish icon cache update"); @@ -320,9 +322,9 @@ public class LoaderTask implements Runnable { // We can only query for shortcuts when the user is unlocked. if (userUnlocked) { - List<ShortcutInfo> pinnedShortcuts = + DeepShortcutManager.QueryResult pinnedShortcuts = mShortcutManager.queryForPinnedShortcuts(null, user); - if (mShortcutManager.wasLastCallSuccess()) { + if (pinnedShortcuts.wasSuccess()) { for (ShortcutInfo shortcut : pinnedShortcuts) { shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut); @@ -387,7 +389,9 @@ public class LoaderTask implements Runnable { boolean validTarget = TextUtils.isEmpty(targetPkg) || mLauncherApps.isPackageEnabledForProfile(targetPkg, c.user); - if (cn != null && validTarget) { + // If it's a deep shortcut, we'll use pinned shortcuts to restore it + if (cn != null && validTarget && c.itemType + != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { // If the apk is present and the shortcut points to a specific // component. @@ -535,7 +539,7 @@ public class LoaderTask implements Runnable { info.spanX = 1; info.spanY = 1; info.runtimeStatusFlags |= disabledState; - if (isSafeMode && !Utilities.isSystemApp(context, intent)) { + if (isSafeMode && !isSystemApp(context, intent)) { info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE; } @@ -584,10 +588,19 @@ public class LoaderTask implements Runnable { int appWidgetId = c.getInt(appWidgetIdIndex); String savedProvider = c.getString(appWidgetProviderIndex); - - final ComponentName component = - ComponentName.unflattenFromString(savedProvider); - + final ComponentName component; + + boolean isSearchWidget = (c.getInt(optionsIndex) + & LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != 0; + if (isSearchWidget) { + component = QsbContainerView.getSearchComponentName(context); + if (component == null) { + c.markDeleted("Discarding SearchWidget without packagename "); + continue; + } + } else { + component = ComponentName.unflattenFromString(savedProvider); + } final boolean isIdValid = !c.hasRestoreFlag( LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); final boolean wasProviderReady = !c.hasRestoreFlag( @@ -597,9 +610,7 @@ public class LoaderTask implements Runnable { widgetProvidersMap = mAppWidgetManager.getAllProvidersMap(); } final AppWidgetProviderInfo provider = widgetProvidersMap.get( - new ComponentKey( - ComponentName.unflattenFromString(savedProvider), - c.user)); + new ComponentKey(component, c.user)); final boolean isProviderReady = isValidProvider(provider); if (!isSafeMode && !customWidget && @@ -665,6 +676,7 @@ public class LoaderTask implements Runnable { c.applyCommonProperties(appWidgetInfo); appWidgetInfo.spanX = c.getInt(spanXIndex); appWidgetInfo.spanY = c.getInt(spanYIndex); + appWidgetInfo.options = c.getInt(optionsIndex); appWidgetInfo.user = c.user; if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) { @@ -710,7 +722,7 @@ public class LoaderTask implements Runnable { } } } finally { - Utilities.closeSilently(c); + IOUtils.closeSilently(c); } // Break early if we've stopped loading @@ -750,8 +762,8 @@ public class LoaderTask implements Runnable { } // Sort the folder items, update ranks, and make sure all preview items are high res. - FolderIconPreviewVerifier verifier = - new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); + FolderGridOrganizer verifier = + new FolderGridOrganizer(mApp.getInvariantDeviceProfile()); for (FolderInfo folder : mBgDataModel.folders) { Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); verifier.setFolderInfo(folder); @@ -778,7 +790,7 @@ public class LoaderTask implements Runnable { new SdCardAvailableReceiver(mApp, pendingPackages), new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, - new Handler(LauncherModel.getWorkerLooper())); + MODEL_EXECUTOR.getHandler()); } } } @@ -836,7 +848,7 @@ public class LoaderTask implements Runnable { } } - mBgAllAppsList.added = new ArrayList<>(); + mBgAllAppsList.getAndResetChangeFlag(); return allActivityList; } diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java index b353810ef..2bd6cd4db 100644 --- a/src/com/android/launcher3/model/ModelPreload.java +++ b/src/com/android/launcher3/model/ModelPreload.java @@ -18,7 +18,6 @@ package com.android.launcher3.model; import android.content.Context; import android.util.Log; -import com.android.launcher3.AllAppsList; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.ModelUpdateTask; diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 4ce2f4ba9..c69ace946 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -16,6 +16,8 @@ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentValues; @@ -31,23 +33,25 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel; -import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; -import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.logging.FileLog; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.ItemInfoMatcher; -import com.android.launcher3.util.LooperExecutor; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * Class for handling model updates. @@ -61,7 +65,6 @@ public class ModelWriter { private final BgDataModel mBgDataModel; private final Handler mUiHandler; - private final Executor mWorkerExecutor; private final boolean mHasVerticalHotseat; private final boolean mVerifyChanges; @@ -74,7 +77,6 @@ public class ModelWriter { mContext = context; mModel = model; mBgDataModel = dataModel; - mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper()); mHasVerticalHotseat = hasVerticalHotseat; mVerifyChanges = verifyChanges; mUiHandler = new Handler(Looper.getMainLooper()); @@ -194,7 +196,7 @@ public class ModelWriter { item.spanX = spanX; item.spanY = spanY; - mWorkerExecutor.execute(new UpdateItemRunnable(item, () -> + ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> new ContentWriter(mContext) .put(Favorites.CONTAINER, item.container) .put(Favorites.CELLX, item.cellX) @@ -209,7 +211,7 @@ public class ModelWriter { * Update an item to the database in a specified container. */ public void updateItemInDatabase(ItemInfo item) { - mWorkerExecutor.execute(new UpdateItemRunnable(item, () -> { + ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> { ContentWriter writer = new ContentWriter(mContext); item.onAddToDatabase(writer); return writer; @@ -229,7 +231,7 @@ public class ModelWriter { ModelVerifier verifier = new ModelVerifier(); final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); - mWorkerExecutor.execute(() -> { + ((Executor) MODEL_EXECUTOR).execute(() -> { // Write the item on background thread, as some properties might have been updated in // the background. final ContentWriter writer = new ContentWriter(mContext); @@ -263,9 +265,12 @@ public class ModelWriter { /** * Removes the specified items from the database */ - public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) { + public void deleteItemsFromDatabase(final Collection<? extends ItemInfo> items) { ModelVerifier verifier = new ModelVerifier(); - + FileLog.d(TAG, "removing items from db " + items.stream().map( + (item) -> item.getTargetComponent() == null ? "" + : item.getTargetComponent().getPackageName()).collect( + Collectors.joining(",")), new Exception()); enqueueDeleteRunnable(() -> { for (ItemInfo item : items) { final Uri uri = Favorites.getContentUri(item.id); @@ -333,14 +338,14 @@ public class ModelWriter { if (mPreparingToUndo) { mDeleteRunnables.add(r); } else { - mWorkerExecutor.execute(r); + ((Executor) MODEL_EXECUTOR).execute(r); } } public void commitDelete() { mPreparingToUndo = false; for (Runnable runnable : mDeleteRunnables) { - mWorkerExecutor.execute(runnable); + ((Executor) MODEL_EXECUTOR).execute(runnable); } mDeleteRunnables.clear(); } diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index 9fcab3887..802cbc7c5 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -19,20 +19,18 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -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.model.BgDataModel.Callbacks; import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.util.InstantAppResolver; -import java.util.ArrayList; import java.util.HashSet; /** @@ -65,41 +63,11 @@ public class PackageInstallStateChangedTask extends BaseModelUpdateTask { } 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); - } - } - } - } + PromiseAppInfo updated = apps.updatePromiseInstallInfo(mInstallInfo); 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); - } - }); + scheduleCallbackTask(c -> c.bindPromiseAppProgressUpdated(updated)); } + bindApplicationsIfNeeded(); } synchronized (dataModel) { diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java index baeaa9492..3ef48cd8f 100644 --- a/src/com/android/launcher3/model/PackageItemInfo.java +++ b/src/com/android/launcher3/model/PackageItemInfo.java @@ -17,6 +17,7 @@ package com.android.launcher3.model; import com.android.launcher3.ItemInfoWithIcon; +import com.android.launcher3.LauncherSettings; /** * Represents a {@link Package} in the widget tray section. @@ -30,10 +31,21 @@ public class PackageItemInfo extends ItemInfoWithIcon { public PackageItemInfo(String packageName) { this.packageName = packageName; + this.itemType = LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE; + } + + public PackageItemInfo(PackageItemInfo copy) { + this.packageName = copy.packageName; + this.itemType = LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE; } @Override protected String dumpProperties() { return super.dumpProperties() + " packageName=" + packageName; } + + @Override + public PackageItemInfo clone() { + return new PackageItemInfo(this); + } } diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 4428c8e6d..d6ebaaf10 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.model; +import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; + import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -23,23 +25,19 @@ import android.os.Process; import android.os.UserHandle; import android.util.Log; -import com.android.launcher3.AllAppsList; -import com.android.launcher3.AppInfo; -import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; -import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -49,6 +47,7 @@ import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.SafeCloseable; import java.util.ArrayList; import java.util.Arrays; @@ -57,6 +56,7 @@ import java.util.HashSet; import java.util.List; import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; +import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON; /** * Handles updates due to changes in package manager (app installed/updated/removed) @@ -100,6 +100,8 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { FlagOp flagOp = FlagOp.NO_OP; final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages)); ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser); + final HashSet<ComponentName> removedComponents = new HashSet<>(); + switch (mOp) { case OP_ADD: { for (int i = 0; i < N; i++) { @@ -119,17 +121,21 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { break; } case OP_UPDATE: - for (int i = 0; i < N; i++) { - if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); - iconCache.updateIconsForPkg(packages[i], mUser); - appsList.updatePackage(context, packages[i], mUser); - app.getWidgetCache().removePackage(packages[i], mUser); + try (SafeCloseable t = + appsList.trackRemoves(a -> removedComponents.add(a.componentName))) { + for (int i = 0; i < N; i++) { + if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); + iconCache.updateIconsForPkg(packages[i], mUser); + appsList.updatePackage(context, packages[i], mUser); + app.getWidgetCache().removePackage(packages[i], mUser); + } } // Since package was just updated, the target must be available now. flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE); break; case OP_REMOVE: { for (int i = 0; i < N; i++) { + FileLog.d(TAG, "Removing app icon" + packages[i]); iconCache.removeIconsForPkg(packages[i], mUser); } // Fall through @@ -160,23 +166,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { break; } - final ArrayList<AppInfo> addedOrModified = new ArrayList<>(); - addedOrModified.addAll(appsList.added); - appsList.added.clear(); - addedOrModified.addAll(appsList.modified); - appsList.modified.clear(); - if (!addedOrModified.isEmpty()) { - scheduleCallbackTask((callbacks) -> callbacks.bindAppsAddedOrUpdated(addedOrModified)); - } - - final ArrayList<AppInfo> removedApps = new ArrayList<>(appsList.removed); - appsList.removed.clear(); - final HashSet<ComponentName> removedComponents = new HashSet<>(); - if (mOp == OP_UPDATE) { - for (AppInfo ai : removedApps) { - removedComponents.add(ai.componentName); - } - } + bindApplicationsIfNeeded(); final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>(); @@ -234,7 +224,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { isTargetValid = LauncherAppsCompat.getInstance(context) .isActivityEnabledForProfile(cn, mUser); } - if (si.hasStatusFlag(FLAG_AUTOINSTALL_ICON)) { + if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) { if (updateWorkspaceItemIntent(context, si, packageName)) { infoUpdated = true; } else if (si.hasPromiseIconUi()) { @@ -302,12 +292,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { } if (!widgets.isEmpty()) { - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindWidgetsRestored(widgets); - } - }); + scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets)); } } @@ -338,16 +323,6 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); } - if (!removedApps.isEmpty()) { - // Remove corresponding apps from All-Apps - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindAppInfosRemoved(removedApps); - } - }); - } - if (Utilities.ATLEAST_OREO && mOp == OP_ADD) { // Load widgets for the new package. Changes due to app updates are handled through // AppWidgetHost events, this is just to initialize the long-press options. diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index 8528228f2..6c358b170 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -19,7 +19,6 @@ import android.content.Context; import android.content.pm.ShortcutInfo; import android.os.UserHandle; -import com.android.launcher3.AllAppsList; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; @@ -65,7 +64,7 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask { for (ItemInfo itemInfo : dataModel.itemsIdMap) { if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo; - if (si.getIntent().getPackage().equals(mPackageName) && si.user.equals(mUser)) { + if (mPackageName.equals(si.getIntent().getPackage()) && si.user.equals(mUser)) { keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si); allIds.add(si.getDeepShortcutId()); } diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 2cb256e09..4b773d720 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.pm.ShortcutInfo; import android.os.UserHandle; -import com.android.launcher3.AllAppsList; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; @@ -37,7 +36,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; /** * Task to handle changing of lock state of the user @@ -58,9 +56,9 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>(); if (isUserUnlocked) { - List<ShortcutInfo> shortcuts = + DeepShortcutManager.QueryResult shortcuts = deepShortcutManager.queryForPinnedShortcuts(null, mUser); - if (deepShortcutManager.wasLastCallSuccess()) { + if (shortcuts.wasSuccess()) { for (ShortcutInfo shortcut : shortcuts) { pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut); } diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java index 32410a64b..021fb30c5 100644 --- a/src/com/android/launcher3/notification/NotificationItemView.java +++ b/src/com/android/launcher3/notification/NotificationItemView.java @@ -16,6 +16,8 @@ package com.android.launcher3.notification; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; + import android.app.Notification; import android.content.Context; import android.graphics.Color; @@ -28,13 +30,11 @@ import android.widget.TextView; import com.android.launcher3.R; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.popup.PopupContainerWithArrow; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.util.Themes; import java.util.List; -import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL; - /** * Utility class to manage notification UI */ @@ -49,7 +49,7 @@ public class NotificationItemView { private final TextView mHeaderCount; private final NotificationMainView mMainView; private final NotificationFooterLayout mFooter; - private final SwipeDetector mSwipeDetector; + private final SingleAxisSwipeDetector mSwipeDetector; private final View mIconView; private final View mHeader; @@ -74,8 +74,8 @@ public class NotificationItemView { mHeader = container.findViewById(R.id.header); mDivider = container.findViewById(R.id.divider); - mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL); - mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); + mSwipeDetector = new SingleAxisSwipeDetector(mContext, mMainView, HORIZONTAL); + mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false); mMainView.setSwipeDetector(mSwipeDetector); mFooter.setContainer(this); } diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java index e57a051f8..10378ee43 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -16,6 +16,7 @@ package com.android.launcher3.notification; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; import android.annotation.TargetApi; @@ -31,7 +32,8 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; -import com.android.launcher3.LauncherModel; +import androidx.annotation.Nullable; + import com.android.launcher3.util.IntSet; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.SecureSettingsObserver; @@ -43,8 +45,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import androidx.annotation.Nullable; - /** * A {@link NotificationListenerService} that sends updates to its * {@link NotificationsChangedListener} when notifications are posted or canceled, @@ -141,7 +141,7 @@ public class NotificationListener extends NotificationListenerService { public NotificationListener() { super(); - mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback); + mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback); mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback); sNotificationListenerInstance = this; } diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 78627ecc1..b67adbb2c 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -38,8 +38,9 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.touch.BaseSwipeDetector; import com.android.launcher3.touch.OverScroll; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.Themes; @@ -48,7 +49,7 @@ import com.android.launcher3.util.Themes; * e.g. icon + title + text. */ @TargetApi(Build.VERSION_CODES.N) -public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener { +public class NotificationMainView extends FrameLayout implements SingleAxisSwipeDetector.Listener { private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION = new FloatProperty<NotificationMainView>("contentTranslation") { @@ -75,7 +76,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L private TextView mTextView; private View mIconView; - private SwipeDetector mSwipeDetector; + private SingleAxisSwipeDetector mSwipeDetector; public NotificationMainView(Context context) { this(context, null, 0); @@ -107,7 +108,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L mIconView = findViewById(R.id.popup_item_icon); } - public void setSwipeDetector(SwipeDetector swipeDetector) { + public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) { mSwipeDetector = swipeDetector; } @@ -173,7 +174,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L LauncherLogProto.ItemType.NOTIFICATION); } - // SwipeDetector.Listener's + // SingleAxisSwipeDetector.Listener's @Override public void onDragStart(boolean start) { } @@ -187,7 +188,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { final boolean willExit; final float endTranslation; final float startTranslation = mTextAndBackground.getTranslationX(); @@ -195,7 +196,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L if (!canChildBeDismissed()) { willExit = false; endTranslation = 0; - } else if (fling) { + } else if (mSwipeDetector.isFling(velocity)) { willExit = true; endTranslation = velocity < 0 ? - getWidth() : getWidth(); } else if (Math.abs(startTranslation) > getWidth() / 2) { @@ -206,7 +207,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L endTranslation = 0; } - long duration = SwipeDetector.calculateDuration(velocity, + long duration = BaseSwipeDetector.calculateDuration(velocity, (endTranslation - startTranslation) / getWidth()); mContentTranslateAnimator.removeAllListeners(); diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index 28000b977..98f7fd853 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -360,10 +360,14 @@ public abstract class ArrowPopup extends AbstractFloatingView { final TimeInterpolator revealInterpolator = ACCEL_DEACCEL; // Rectangular reveal. + mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); final ValueAnimator revealAnim = createOpenCloseOutlineProvider() .createRevealAnimator(this, false); revealAnim.setDuration(revealDuration); revealAnim.setInterpolator(revealInterpolator); + // Clip the popup to the initial outline while the notification dot and arrow animate. + revealAnim.start(); + revealAnim.pause(); ValueAnimator fadeIn = ValueAnimator.ofFloat(0, 1); fadeIn.setDuration(revealDuration + arrowDuration); @@ -399,7 +403,6 @@ public abstract class ArrowPopup extends AbstractFloatingView { if (!mIsOpen) { return; } - mEndRect.setEmpty(); if (getOutlineProvider() instanceof RevealOutlineAnimation) { ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect); } @@ -471,9 +474,6 @@ public abstract class ArrowPopup extends AbstractFloatingView { mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth, arrowCenterY); - if (mEndRect.isEmpty()) { - mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); - } return new RoundedRectRevealOutlineProvider (arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect); diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 15fb4cea6..e8ac1d4c9 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -24,6 +24,7 @@ import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFI import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType; import static com.android.launcher3.userevent.nano.LauncherLogProto.Target; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.animation.AnimatorSet; import android.animation.LayoutTransition; @@ -36,6 +37,7 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; +import android.util.Log; import android.util.Pair; import android.view.MotionEvent; import android.view.View; @@ -50,7 +52,6 @@ import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.ItemInfo; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import com.android.launcher3.R; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate; @@ -65,6 +66,7 @@ import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.PackageUserKey; @@ -166,7 +168,10 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, } public OnClickListener getItemClickListener() { - return ItemClickHandler.INSTANCE; + return (view) -> { + ItemClickHandler.INSTANCE.onClick(view); + close(true); + }; } @Override @@ -191,6 +196,9 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, * @return the container if shown or null. */ public static PopupContainerWithArrow showForIcon(BubbleTextView icon) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_CONTEXT_MENU, "showForIcon"); + } Launcher launcher = Launcher.getLauncher(icon.getContext()); if (getOpen(launcher) != null) { // There is already an items container open, so don't open this one. @@ -232,6 +240,9 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, protected void populateAndShow( BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_CONTEXT_MENU, "populateAndShow"); + } PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider(); populateAndShow(icon, popupDataProvider.getShortcutCountForItem(item), @@ -303,8 +314,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, setLayoutTransition(new LayoutTransition()); // Load the shortcuts on a background thread and update the container as it animates. - final Looper workerLooper = LauncherModel.getWorkerLooper(); - new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable( + MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable( mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()), this, mShortcuts, notificationKeys)); } @@ -565,9 +575,12 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, @Override protected void closeComplete() { + PopupContainerWithArrow openPopup = getOpen(mLauncher); + if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) { + mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); + mOriginalIcon.setForceHideDot(false); + } super.closeComplete(); - mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); - mOriginalIcon.setForceHideDot(false); } @Override diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index 78bd81b46..a87b7b897 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -1,14 +1,11 @@ package com.android.launcher3.popup; -import static com.android.launcher3.userevent.nano.LauncherLogProto.Action; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.graphics.drawable.Icon; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.View; @@ -20,23 +17,30 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action; +import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.widget.WidgetsBottomSheet; import java.util.List; - /** * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an * onClickListener that depends on the item that the shortcut services. * * Example system shortcuts, defined as inner classes, include Widgets and AppInfo. + * @param <T> */ -public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo { +public abstract class SystemShortcut<T extends BaseDraggingActivity> + extends ItemInfo { private final int mIconResId; private final int mLabelResId; private final Icon mIcon; @@ -202,6 +206,27 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite } } + public static class DismissPrediction extends SystemShortcut<Launcher> { + public DismissPrediction() { + super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label); + } + + @Override + public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) { + if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null; + if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null; + return (view) -> { + PopupContainerWithArrow.closeAllOpenViews(activity); + activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, + ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS); + AppLaunchTracker.INSTANCE.get(view.getContext()) + .onDismissApp(itemInfo.getTargetComponent(), + itemInfo.user, + AppLaunchTracker.CONTAINER_PREDICTIONS); + }; + } + } + protected static void dismissTaskMenuView(BaseDraggingActivity activity) { AbstractFloatingView.closeOpenViews(activity, true, AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java index 37a209289..dfcc2f822 100644 --- a/src/com/android/launcher3/popup/SystemShortcutFactory.java +++ b/src/com/android/launcher3/popup/SystemShortcutFactory.java @@ -39,7 +39,9 @@ public class SystemShortcutFactory implements ResourceBasedOverride { @SuppressWarnings("unused") public SystemShortcutFactory() { this(new SystemShortcut.AppInfo(), - new SystemShortcut.Widgets(), new SystemShortcut.Install()); + new SystemShortcut.Widgets(), + new SystemShortcut.Install(), + new SystemShortcut.DismissPrediction()); } protected SystemShortcutFactory(SystemShortcut... shortcuts) { @@ -53,6 +55,7 @@ public class SystemShortcutFactory implements ResourceBasedOverride { systemShortcuts.add(systemShortcut); } } + return systemShortcuts; } } diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java index 7b62f53fe..970a03e80 100644 --- a/src/com/android/launcher3/provider/ImportDataTask.java +++ b/src/com/android/launcher3/provider/ImportDataTask.java @@ -42,7 +42,6 @@ import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; -import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; @@ -50,6 +49,7 @@ import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.PackageManagerHelper; import java.net.URISyntaxException; import java.util.ArrayList; @@ -223,7 +223,7 @@ public class ImportDataTask { case Favorites.ITEM_TYPE_SHORTCUT: case Favorites.ITEM_TYPE_APPLICATION: { intent = Intent.parseUri(c.getString(intentIndex), 0); - if (Utilities.isLauncherAppTarget(intent)) { + if (PackageManagerHelper.isLauncherAppTarget(intent)) { type = Favorites.ITEM_TYPE_APPLICATION; } else { values.put(Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index d643a0b49..fb335516f 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -16,8 +16,6 @@ package com.android.launcher3.provider; -import static com.android.launcher3.Utilities.getIntArrayFromString; -import static com.android.launcher3.Utilities.getStringFromIntArray; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; import android.app.backup.BackupManager; @@ -40,6 +38,7 @@ import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; import com.android.launcher3.logging.FileLog; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; +import com.android.launcher3.util.IntArray; import com.android.launcher3.util.LogConfig; import java.io.InvalidObjectException; @@ -240,8 +239,8 @@ public class RestoreDbTask { SharedPreferences prefs = Utilities.getPrefs(context); if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) { AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, - getIntArrayFromString(prefs.getString(APPWIDGET_OLD_IDS, "")), - getIntArrayFromString(prefs.getString(APPWIDGET_IDS, ""))); + IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(), + IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray()); } else { FileLog.d(TAG, "No app widget ids to restore."); } @@ -253,8 +252,8 @@ public class RestoreDbTask { public static void setRestoredAppWidgetIds(Context context, @NonNull int[] oldIds, @NonNull int[] newIds) { Utilities.getPrefs(context).edit() - .putString(APPWIDGET_OLD_IDS, getStringFromIntArray(oldIds)) - .putString(APPWIDGET_IDS, getStringFromIntArray(newIds)) + .putString(APPWIDGET_OLD_IDS, IntArray.wrap(oldIds).toConcatString()) + .putString(APPWIDGET_IDS, IntArray.wrap(newIds).toConcatString()) .commit(); } diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java index 857ea05d6..0eb428527 100644 --- a/src/com/android/launcher3/qsb/QsbContainerView.java +++ b/src/com/android/launcher3/qsb/QsbContainerView.java @@ -32,12 +32,16 @@ import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.os.Bundle; +import android.provider.Settings; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; @@ -55,6 +59,74 @@ import com.android.launcher3.graphics.FragmentWithPreview; */ public class QsbContainerView extends FrameLayout { + public static final String SEARCH_PROVIDER_SETTINGS_KEY = "SEARCH_PROVIDER_PACKAGE_NAME"; + + /** + * Returns the package name for user configured search provider or from searchManager + * @param context + * @return String + */ + @Nullable + public static String getSearchWidgetPackageName(@NonNull Context context) { + String providerPkg = Settings.Global.getString(context.getContentResolver(), + SEARCH_PROVIDER_SETTINGS_KEY); + if (providerPkg == null) { + SearchManager searchManager = context.getSystemService(SearchManager.class); + ComponentName componentName = searchManager.getGlobalSearchActivity(); + if (componentName != null) { + providerPkg = searchManager.getGlobalSearchActivity().getPackageName(); + } + } + return providerPkg; + } + + /** + * returns it's AppWidgetProviderInfo using package name from getSearchWidgetPackageName + * @param context + * @return AppWidgetProviderInfo + */ + @Nullable + public static AppWidgetProviderInfo getSearchWidgetProviderInfo(@NonNull Context context) { + String providerPkg = getSearchWidgetPackageName(context); + if (providerPkg == null) { + return null; + } + + AppWidgetProviderInfo defaultWidgetForSearchPackage = null; + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + for (AppWidgetProviderInfo info : + appWidgetManager.getInstalledProvidersForPackage(providerPkg, null)) { + if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) { + if ((info.widgetCategory + & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) { + return info; + } else if (defaultWidgetForSearchPackage == null) { + defaultWidgetForSearchPackage = info; + } + } + } + return defaultWidgetForSearchPackage; + } + + /** + * returns componentName for searchWidget if package name is known. + */ + @Nullable + public static ComponentName getSearchComponentName(@NonNull Context context) { + AppWidgetProviderInfo providerInfo = + QsbContainerView.getSearchWidgetProviderInfo(context); + if (providerInfo != null) { + return providerInfo.provider; + } else { + String pkgName = QsbContainerView.getSearchWidgetPackageName(context); + if (pkgName != null) { + //we don't know the class name yet. we'll put the package name as placeholder + return new ComponentName(pkgName, pkgName); + } + return null; + } + } + public QsbContainerView(Context context) { super(context); } @@ -101,7 +173,7 @@ public class QsbContainerView extends FrameLayout { protected QsbWidgetHost createHost() { return new QsbWidgetHost(getContext(), QSB_WIDGET_HOST_ID, - (c) -> new QsbWidgetHostView(c)); + (c) -> new QsbWidgetHostView(c), this::rebindFragment); } private FrameLayout mWrapper; @@ -111,9 +183,9 @@ public class QsbContainerView extends FrameLayout { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mWrapper = new FrameLayout(getContext()); - // Only add the view when enabled if (isQsbEnabled()) { + mQsbWidgetHost.startListening(); mWrapper.addView(createQsb(mWrapper)); } return mWrapper; @@ -155,7 +227,8 @@ public class QsbContainerView extends FrameLayout { } if (isWidgetBound) { - mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId, mWidgetInfo); + mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId, + mWidgetInfo); mQsb.setId(R.id.qsb_widget); if (!isInPreviewMode()) { @@ -163,7 +236,6 @@ public class QsbContainerView extends FrameLayout { .getAppWidgetOptions(widgetId), opts)) { mQsb.updateAppWidgetOptions(opts); } - mQsbWidgetHost.startListening(); } return mQsb; } @@ -246,43 +318,32 @@ public class QsbContainerView extends FrameLayout { return v; } + /** * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX} - * provided by the same package which is set to be global search activity. + * provided by the package from getSearchProviderPackageName * If widgetCategory is not supported, or no such widget is found, returns the first widget * provided by the package. */ protected AppWidgetProviderInfo getSearchWidgetProvider() { - SearchManager searchManager = - (SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE); - ComponentName searchComponent = searchManager.getGlobalSearchActivity(); - if (searchComponent == null) return null; - String providerPkg = searchComponent.getPackageName(); - - AppWidgetProviderInfo defaultWidgetForSearchPackage = null; - - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext()); - for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) { - if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) { - if ((info.widgetCategory - & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) { - return info; - } else if (defaultWidgetForSearchPackage == null) { - defaultWidgetForSearchPackage = info; - } - } - } - return defaultWidgetForSearchPackage; + return getSearchWidgetProviderInfo(getContext()); } } public static class QsbWidgetHost extends AppWidgetHost { private final WidgetViewFactory mViewFactory; + private final WidgetProvidersUpdateCallback mWidgetsUpdateCallback; - public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) { + public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory, + WidgetProvidersUpdateCallback widgetProvidersUpdateCallback) { super(context, hostId); mViewFactory = viewFactory; + mWidgetsUpdateCallback = widgetProvidersUpdateCallback; + } + + public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) { + this(context, hostId, viewFactory, null); } @Override @@ -290,6 +351,14 @@ public class QsbContainerView extends FrameLayout { Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { return mViewFactory.newView(context); } + + @Override + protected void onProvidersChanged() { + super.onProvidersChanged(); + if (mWidgetsUpdateCallback != null) { + mWidgetsUpdateCallback.onProvidersUpdated(); + } + } } public interface WidgetViewFactory { @@ -298,6 +367,17 @@ public class QsbContainerView extends FrameLayout { } /** + * Callback interface for packages list update. + */ + @FunctionalInterface + public interface WidgetProvidersUpdateCallback { + /** + * Gets called when widget providers list changes + */ + void onProvidersUpdated(); + } + + /** * Returns true if {@param original} contains all entries defined in {@param updates} and * have the same value. * The comparison uses {@link Object#equals(Object)} to compare the values. @@ -316,4 +396,5 @@ public class QsbContainerView extends FrameLayout { } return true; } + } diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java index c6370c5c5..a23cd6d2e 100644 --- a/src/com/android/launcher3/states/InternalStateHandler.java +++ b/src/com/android/launcher3/states/InternalStateHandler.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.states; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.Intent; import android.os.Binder; import android.os.Bundle; @@ -22,8 +24,7 @@ import android.os.IBinder; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel.Callbacks; -import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.model.BgDataModel.Callbacks; import java.lang.ref.WeakReference; @@ -94,16 +95,12 @@ public abstract class InternalStateHandler extends Binder { private static class Scheduler implements Runnable { private WeakReference<InternalStateHandler> mPendingHandler = new WeakReference<>(null); - private MainThreadExecutor mMainThreadExecutor; public void schedule(InternalStateHandler handler) { synchronized (this) { mPendingHandler = new WeakReference<>(handler); - if (mMainThreadExecutor == null) { - mMainThreadExecutor = new MainThreadExecutor(); - } } - mMainThreadExecutor.execute(this); + MAIN_EXECUTOR.execute(this); } @Override diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index 790a2e844..c6de9ca90 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -17,6 +17,8 @@ package com.android.launcher3.testing; import static android.graphics.Bitmap.Config.ARGB_8888; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; @@ -27,8 +29,8 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherState; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.util.ResourceBasedOverride; @@ -82,7 +84,7 @@ public class TestInformationHandler implements ResourceBasedOverride { } case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: { - response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true); + response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, isLauncherInitialized()); break; } @@ -95,20 +97,20 @@ public class TestInformationHandler implements ResourceBasedOverride { break; case TestProtocol.REQUEST_FREEZE_APP_LIST: - new MainThreadExecutor().execute(() -> + MAIN_EXECUTOR.execute(() -> mLauncher.getAppsView().getAppsStore().enableDeferUpdates( AllAppsStore.DEFER_UPDATES_TEST)); break; case TestProtocol.REQUEST_UNFREEZE_APP_LIST: - new MainThreadExecutor().execute(() -> + MAIN_EXECUTOR.execute(() -> mLauncher.getAppsView().getAppsStore().disableDeferUpdates( AllAppsStore.DEFER_UPDATES_TEST)); break; case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: { try { - final int deferUpdatesFlags = new MainThreadExecutor().submit(() -> + final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() -> mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get(); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, deferUpdatesFlags); @@ -149,7 +151,19 @@ public class TestInformationHandler implements ResourceBasedOverride { mLeaks.add(bitmap); break; } + + case TestProtocol.REQUEST_ICON_HEIGHT: { + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, + mDeviceProfile.allAppsCellHeightPx); + break; + } } return response; } -}
\ No newline at end of file + + protected boolean isLauncherInitialized() { + final LauncherModel model = LauncherAppState.getInstance(mContext).getModel(); + return model.getCallback() == null || model.isModelLoaded(); + } +} + diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 232a764e0..07ddbdc4e 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -66,6 +66,8 @@ public final class TestProtocol { "all-apps-to-overview-swipe-height"; public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT = "home-to-all-apps-swipe-height"; + public static final String REQUEST_ICON_HEIGHT = + "icon-height"; public static final String REQUEST_HOTSEAT_TOP = "hotseat-top"; public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized"; public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list"; @@ -84,6 +86,5 @@ public final class TestProtocol { public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824"; public static final String NO_DRAG_TO_WORKSPACE = "b/138729456"; public static final String APP_NOT_DISABLED = "b/139891609"; - public static final String ALL_APPS_UPON_RECENTS = "b/139941530"; - public static final String STABLE_STATE_MISMATCH = "b/140311911"; + public static final String NO_CONTEXT_MENU = "b/141770616"; } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index c5ba5bab6..60f6ee9c5 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -53,7 +53,7 @@ import com.android.launcher3.util.TouchController; * TouchController for handling state changes */ public abstract class AbstractStateChangeTouchController - implements TouchController, SwipeDetector.Listener { + implements TouchController, SingleAxisSwipeDetector.Listener { // Progress after which the transition is assumed to be a success in case user does not fling public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; @@ -65,8 +65,8 @@ public abstract class AbstractStateChangeTouchController protected final long ATOMIC_DURATION = getAtomicDuration(); protected final Launcher mLauncher; - protected final SwipeDetector mDetector; - protected final SwipeDetector.Direction mSwipeDirection; + protected final SingleAxisSwipeDetector mDetector; + protected final SingleAxisSwipeDetector.Direction mSwipeDirection; private boolean mNoIntercept; private boolean mIsLogContainerSet; @@ -101,9 +101,9 @@ public abstract class AbstractStateChangeTouchController private float mAtomicComponentsStartProgress; - public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) { + public AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) { mLauncher = l; - mDetector = new SwipeDetector(l, this, dir); + mDetector = new SingleAxisSwipeDetector(l, this, dir); mSwipeDirection = dir; } @@ -127,7 +127,7 @@ public abstract class AbstractStateChangeTouchController boolean ignoreSlopWhenSettling = false; if (mCurrentAnimation != null) { - directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH; + directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH; ignoreSlopWhenSettling = true; } else { directionsToDetectScroll = getSwipeDirection(); @@ -152,10 +152,10 @@ public abstract class AbstractStateChangeTouchController LauncherState fromState = mLauncher.getStateManager().getState(); int swipeDirection = 0; if (getTargetState(fromState, true /* isDragTowardPositive */) != fromState) { - swipeDirection |= SwipeDetector.DIRECTION_POSITIVE; + swipeDirection |= SingleAxisSwipeDetector.DIRECTION_POSITIVE; } if (getTargetState(fromState, false /* isDragTowardPositive */) != fromState) { - swipeDirection |= SwipeDetector.DIRECTION_NEGATIVE; + swipeDirection |= SingleAxisSwipeDetector.DIRECTION_NEGATIVE; } return swipeDirection; } @@ -369,7 +369,8 @@ public abstract class AbstractStateChangeTouchController } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { + boolean fling = mDetector.isFling(velocity); final int logAction = fling ? Touch.FLING : Touch.SWIPE; boolean blockedFling = fling && mFlingBlockCheck.isBlocked(); @@ -406,7 +407,7 @@ public abstract class AbstractStateChangeTouchController } else { startProgress = Utilities.boundToRange(progress + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f); - duration = SwipeDetector.calculateDuration(velocity, + duration = BaseSwipeDetector.calculateDuration(velocity, endProgress - Math.max(progress, 0)) * durationMultiplier; } } else { @@ -424,7 +425,7 @@ public abstract class AbstractStateChangeTouchController } else { startProgress = Utilities.boundToRange(progress + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f); - duration = SwipeDetector.calculateDuration(velocity, + duration = BaseSwipeDetector.calculateDuration(velocity, Math.min(progress, 1) - endProgress) * durationMultiplier; } } diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java new file mode 100644 index 000000000..12ca5ee7b --- /dev/null +++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java @@ -0,0 +1,268 @@ +/* + * 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.touch; + +import static android.view.MotionEvent.INVALID_POINTER_ID; + +import android.graphics.PointF; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; + +import androidx.annotation.NonNull; + +/** + * Scroll/drag/swipe gesture detector. + * + * Definition of swipe is different from android system in that this detector handles + * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before + * swipe action happens. + * + * @see SingleAxisSwipeDetector + * @see BothAxesSwipeDetector + */ +public abstract class BaseSwipeDetector { + + private static final boolean DBG = false; + private static final String TAG = "BaseSwipeDetector"; + private static final float ANIMATION_DURATION = 1200; + /** The minimum release velocity in pixels per millisecond that triggers fling.*/ + private static final float RELEASE_VELOCITY_PX_MS = 1.0f; + private static final PointF sTempPoint = new PointF(); + + private final PointF mDownPos = new PointF(); + private final PointF mLastPos = new PointF(); + protected final boolean mIsRtl; + protected final float mTouchSlop; + protected final float mMaxVelocity; + + private int mActivePointerId = INVALID_POINTER_ID; + private VelocityTracker mVelocityTracker; + private PointF mLastDisplacement = new PointF(); + private PointF mDisplacement = new PointF(); + protected PointF mSubtractDisplacement = new PointF(); + private ScrollState mState = ScrollState.IDLE; + + protected boolean mIgnoreSlopWhenSettling; + + private enum ScrollState { + IDLE, + DRAGGING, // onDragStart, onDrag + SETTLING // onDragEnd + } + + protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) { + mTouchSlop = config.getScaledTouchSlop(); + mMaxVelocity = config.getScaledMaximumFlingVelocity(); + mIsRtl = isRtl; + } + + public static long calculateDuration(float velocity, float progressNeeded) { + // TODO: make these values constants after tuning. + float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity)); + float travelDistance = Math.max(0.2f, progressNeeded); + long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance); + if (DBG) { + Log.d(TAG, String.format( + "calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded)); + } + return duration; + } + + public int getDownX() { + return (int) mDownPos.x; + } + + public int getDownY() { + return (int) mDownPos.y; + } + /** + * There's no touch and there's no animation. + */ + public boolean isIdleState() { + return mState == ScrollState.IDLE; + } + + public boolean isSettlingState() { + return mState == ScrollState.SETTLING; + } + + public boolean isDraggingState() { + return mState == ScrollState.DRAGGING; + } + + public boolean isDraggingOrSettling() { + return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING; + } + + public void finishedScrolling() { + setState(ScrollState.IDLE); + } + + public boolean isFling(float velocity) { + return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS; + } + + public boolean onTouchEvent(MotionEvent ev) { + int actionMasked = ev.getActionMasked(); + if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) { + mVelocityTracker.clear(); + } + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = ev.getPointerId(0); + mDownPos.set(ev.getX(), ev.getY()); + mLastPos.set(mDownPos); + mLastDisplacement.set(0, 0); + mDisplacement.set(0, 0); + + if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { + setState(ScrollState.DRAGGING); + } + break; + //case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_POINTER_UP: + int ptrIdx = ev.getActionIndex(); + int ptrId = ev.getPointerId(ptrIdx); + if (ptrId == mActivePointerId) { + final int newPointerIdx = ptrIdx == 0 ? 1 : 0; + mDownPos.set( + ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), + ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); + mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); + mActivePointerId = ev.getPointerId(newPointerIdx); + } + break; + case MotionEvent.ACTION_MOVE: + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == INVALID_POINTER_ID) { + break; + } + mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x, + ev.getY(pointerIndex) - mDownPos.y); + if (mIsRtl) { + mDisplacement.x = -mDisplacement.x; + } + + // handle state and listener calls. + if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) { + setState(ScrollState.DRAGGING); + } + if (mState == ScrollState.DRAGGING) { + reportDragging(ev); + } + mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + // These are synthetic events and there is no need to update internal values. + if (mState == ScrollState.DRAGGING) { + setState(ScrollState.SETTLING); + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + break; + default: + break; + } + return true; + } + + //------------------- ScrollState transition diagram ----------------------------------- + // + // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING + // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING + // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING + // SETTLING -> (View settled) -> IDLE + + private void setState(ScrollState newState) { + if (DBG) { + Log.d(TAG, "setState:" + mState + "->" + newState); + } + // onDragStart and onDragEnd is reported ONLY on state transition + if (newState == ScrollState.DRAGGING) { + initializeDragging(); + if (mState == ScrollState.IDLE) { + reportDragStart(false /* recatch */); + } else if (mState == ScrollState.SETTLING) { + reportDragStart(true /* recatch */); + } + } + if (newState == ScrollState.SETTLING) { + reportDragEnd(); + } + + mState = newState; + } + + private void initializeDragging() { + if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { + mSubtractDisplacement.set(0, 0); + } else { + mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop; + mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop; + } + } + + protected abstract boolean shouldScrollStart(PointF displacement); + + private void reportDragStart(boolean recatch) { + reportDragStartInternal(recatch); + if (DBG) { + Log.d(TAG, "onDragStart recatch:" + recatch); + } + } + + protected abstract void reportDragStartInternal(boolean recatch); + + private void reportDragging(MotionEvent event) { + if (mDisplacement != mLastDisplacement) { + if (DBG) { + Log.d(TAG, String.format("onDrag disp=%s", mDisplacement)); + } + + mLastDisplacement.set(mDisplacement); + sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x, + mDisplacement.y - mSubtractDisplacement.y); + reportDraggingInternal(sTempPoint, event); + } + } + + protected abstract void reportDraggingInternal(PointF displacement, MotionEvent event); + + private void reportDragEnd() { + mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); + PointF velocity = new PointF(mVelocityTracker.getXVelocity() / 1000, + mVelocityTracker.getYVelocity() / 1000); + if (mIsRtl) { + velocity.x = -velocity.x; + } + if (DBG) { + Log.d(TAG, String.format("onScrollEnd disp=%.1s, velocity=%.1s", + mDisplacement, velocity)); + } + + reportDragEndInternal(velocity); + } + + protected abstract void reportDragEndInternal(PointF velocity); +} diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java new file mode 100644 index 000000000..944391e9b --- /dev/null +++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 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.touch; + +import android.content.Context; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.launcher3.Utilities; + +/** + * Two dimensional scroll/drag/swipe gesture detector that reports x and y displacement/velocity. + */ +public class BothAxesSwipeDetector extends BaseSwipeDetector { + + public static final int DIRECTION_UP = 1 << 0; + // Note that this will track left instead of right in RTL. + public static final int DIRECTION_RIGHT = 1 << 1; + public static final int DIRECTION_DOWN = 1 << 2; + // Note that this will track right instead of left in RTL. + public static final int DIRECTION_LEFT = 1 << 3; + + /* Client of this gesture detector can register a callback. */ + private final Listener mListener; + + private int mScrollDirections; + + public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) { + this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources())); + } + + @VisibleForTesting + protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, + boolean isRtl) { + super(config, isRtl); + mListener = l; + } + + public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { + mScrollDirections = scrollDirectionFlags; + mIgnoreSlopWhenSettling = ignoreSlop; + } + + @Override + protected boolean shouldScrollStart(PointF displacement) { + // Check if the client is interested in scroll in current direction. + boolean canScrollUp = (mScrollDirections & DIRECTION_UP) > 0 + && displacement.y <= -mTouchSlop; + boolean canScrollRight = (mScrollDirections & DIRECTION_RIGHT) > 0 + && displacement.x >= mTouchSlop; + boolean canScrollDown = (mScrollDirections & DIRECTION_DOWN) > 0 + && displacement.y >= mTouchSlop; + boolean canScrollLeft = (mScrollDirections & DIRECTION_LEFT) > 0 + && displacement.x <= -mTouchSlop; + return canScrollUp || canScrollRight || canScrollDown || canScrollLeft; + } + + @Override + protected void reportDragStartInternal(boolean recatch) { + mListener.onDragStart(!recatch); + } + + @Override + protected void reportDraggingInternal(PointF displacement, MotionEvent event) { + mListener.onDrag(displacement, event); + } + + @Override + protected void reportDragEndInternal(PointF velocity) { + mListener.onDragEnd(velocity); + } + + /** Listener to receive updates on the swipe. */ + public interface Listener { + /** @param start whether this was the original drag start, as opposed to a recatch. */ + void onDragStart(boolean start); + + boolean onDrag(PointF displacement, MotionEvent motionEvent); + + void onDragEnd(PointF velocity); + } +} diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java index babbcdd16..86d2b3901 100644 --- a/src/com/android/launcher3/touch/ItemLongClickListener.java +++ b/src/com/android/launcher3/touch/ItemLongClickListener.java @@ -17,10 +17,12 @@ package com.android.launcher3.touch; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; + import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; +import android.util.Log; import android.view.View; import android.view.View.OnLongClickListener; @@ -32,6 +34,9 @@ import com.android.launcher3.Launcher; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; +import com.android.launcher3.testing.TestProtocol; + +import java.util.Arrays; /** * Class to handle long-clicks on workspace items and start drag as a result. @@ -60,7 +65,7 @@ public class ItemLongClickListener { if (info.container >= 0) { Folder folder = Folder.getOpen(launcher); if (folder != null) { - if (!folder.getItemsInReadingOrder().contains(v)) { + if (!folder.getIconsInReadingOrder().contains(v)) { folder.close(true); } else { folder.startDrag(v, dragOptions); @@ -74,10 +79,19 @@ public class ItemLongClickListener { } private static boolean onAllAppsItemLongClick(View v) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick1"); + } Launcher launcher = Launcher.getLauncher(v.getContext()); if (!canStartDrag(launcher)) return false; + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick2"); + } // When we have exited all apps or are in transition, disregard long clicks if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false; + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick3"); + } if (launcher.getWorkspace().isSwitchingState()) return false; // Start the drag diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java new file mode 100644 index 000000000..f2ebc4519 --- /dev/null +++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019 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.touch; + +import android.content.Context; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.launcher3.Utilities; + +/** + * One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL). + */ +public class SingleAxisSwipeDetector extends BaseSwipeDetector { + + public static final int DIRECTION_POSITIVE = 1 << 0; + public static final int DIRECTION_NEGATIVE = 1 << 1; + public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE; + + public static final Direction VERTICAL = new Direction() { + + @Override + boolean isPositive(float displacement) { + // Up + return displacement < 0; + } + + @Override + boolean isNegative(float displacement) { + // Down + return displacement > 0; + } + + @Override + float extractDirection(PointF direction) { + return direction.y; + } + + @Override + boolean canScrollStart(PointF displacement, float touchSlop) { + return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop); + } + + }; + + public static final Direction HORIZONTAL = new Direction() { + + @Override + boolean isPositive(float displacement) { + // Right + return displacement > 0; + } + + @Override + boolean isNegative(float displacement) { + // Left + return displacement < 0; + } + + @Override + float extractDirection(PointF direction) { + return direction.x; + } + + @Override + boolean canScrollStart(PointF displacement, float touchSlop) { + return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop); + } + }; + + private final Direction mDir; + /* Client of this gesture detector can register a callback. */ + private final Listener mListener; + + private int mScrollDirections; + + public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l, + @NonNull Direction dir) { + this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources())); + } + + @VisibleForTesting + protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, + @NonNull Direction dir, boolean isRtl) { + super(config, isRtl); + mListener = l; + mDir = dir; + } + + public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { + mScrollDirections = scrollDirectionFlags; + mIgnoreSlopWhenSettling = ignoreSlop; + } + + public int getScrollDirections() { + return mScrollDirections; + } + + /** + * Returns if the start drag was towards the positive direction or negative. + * + * @see #setDetectableScrollConditions(int, boolean) + * @see #DIRECTION_BOTH + */ + public boolean wasInitialTouchPositive() { + return mDir.isPositive(mDir.extractDirection(mSubtractDisplacement)); + } + + @Override + protected boolean shouldScrollStart(PointF displacement) { + // Reject cases where the angle or slop condition is not met. + if (!mDir.canScrollStart(displacement, mTouchSlop)) { + return false; + } + + // Check if the client is interested in scroll in current direction. + float displacementComponent = mDir.extractDirection(displacement); + return canScrollNegative(displacementComponent) || canScrollPositive(displacementComponent); + } + + private boolean canScrollNegative(float displacement) { + return (mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(displacement); + } + + private boolean canScrollPositive(float displacement) { + return (mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(displacement); + } + + @Override + protected void reportDragStartInternal(boolean recatch) { + mListener.onDragStart(!recatch); + } + + @Override + protected void reportDraggingInternal(PointF displacement, MotionEvent event) { + mListener.onDrag(mDir.extractDirection(displacement), event); + } + + @Override + protected void reportDragEndInternal(PointF velocity) { + float velocityComponent = mDir.extractDirection(velocity); + mListener.onDragEnd(velocityComponent); + } + + /** Listener to receive updates on the swipe. */ + public interface Listener { + /** @param start whether this was the original drag start, as opposed to a recatch. */ + void onDragStart(boolean start); + + // TODO remove + boolean onDrag(float displacement); + + default boolean onDrag(float displacement, MotionEvent event) { + return onDrag(displacement); + } + + void onDragEnd(float velocity); + } + + public abstract static class Direction { + + abstract boolean isPositive(float displacement); + + abstract boolean isNegative(float displacement); + + /** Returns the part of the given {@link PointF} that is relevant to this direction. */ + abstract float extractDirection(PointF point); + + /** Reject cases where the angle or slop condition is not met. */ + abstract boolean canScrollStart(PointF displacement, float touchSlop); + + } +} diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java deleted file mode 100644 index 3777a41ad..000000000 --- a/src/com/android/launcher3/touch/SwipeDetector.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.touch; - -import static android.view.MotionEvent.INVALID_POINTER_ID; - -import android.content.Context; -import android.graphics.PointF; -import android.util.Log; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.ViewConfiguration; - -import com.android.launcher3.Utilities; -import com.android.launcher3.testing.TestProtocol; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; - -/** - * One dimensional scroll/drag/swipe gesture detector. - * - * Definition of swipe is different from android system in that this detector handles - * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before - * swipe action happens - */ -public class SwipeDetector { - - private static final boolean DBG = false; - private static final String TAG = "SwipeDetector"; - - private int mScrollConditions; - public static final int DIRECTION_POSITIVE = 1 << 0; - public static final int DIRECTION_NEGATIVE = 1 << 1; - public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE; - - private static final float ANIMATION_DURATION = 1200; - - protected int mActivePointerId = INVALID_POINTER_ID; - - /** - * The minimum release velocity in pixels per millisecond that triggers fling.. - */ - public static final float RELEASE_VELOCITY_PX_MS = 1.0f; - - /* Scroll state, this is set to true during dragging and animation. */ - private ScrollState mState = ScrollState.IDLE; - - enum ScrollState { - IDLE, - DRAGGING, // onDragStart, onDrag - SETTLING // onDragEnd - } - - public static abstract class Direction { - - abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, - boolean isRtl); - - /** - * Distance in pixels a touch can wander before we think the user is scrolling. - */ - abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos); - - abstract float getVelocity(VelocityTracker tracker, boolean isRtl); - - abstract boolean isPositive(float displacement); - - abstract boolean isNegative(float displacement); - } - - public static final Direction VERTICAL = new Direction() { - - @Override - float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) { - return ev.getY(pointerIndex) - refPoint.y; - } - - @Override - float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { - return Math.abs(ev.getX(pointerIndex) - downPos.x); - } - - @Override - float getVelocity(VelocityTracker tracker, boolean isRtl) { - return tracker.getYVelocity(); - } - - @Override - boolean isPositive(float displacement) { - // Up - return displacement < 0; - } - - @Override - boolean isNegative(float displacement) { - // Down - return displacement > 0; - } - }; - - public static final Direction HORIZONTAL = new Direction() { - - @Override - float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) { - float displacement = ev.getX(pointerIndex) - refPoint.x; - if (isRtl) { - displacement = -displacement; - } - return displacement; - } - - @Override - float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { - return Math.abs(ev.getY(pointerIndex) - downPos.y); - } - - @Override - float getVelocity(VelocityTracker tracker, boolean isRtl) { - float velocity = tracker.getXVelocity(); - if (isRtl) { - velocity = -velocity; - } - return velocity; - } - - @Override - boolean isPositive(float displacement) { - // Right - return displacement > 0; - } - - @Override - boolean isNegative(float displacement) { - // Left - return displacement < 0; - } - }; - - //------------------- ScrollState transition diagram ----------------------------------- - // - // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING - // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING - // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING - // SETTLING -> (View settled) -> IDLE - - private void setState(ScrollState newState) { - if (DBG) { - Log.d(TAG, "setState:" + mState + "->" + newState); - } - // onDragStart and onDragEnd is reported ONLY on state transition - if (newState == ScrollState.DRAGGING) { - initializeDragging(); - if (mState == ScrollState.IDLE) { - reportDragStart(false /* recatch */); - } else if (mState == ScrollState.SETTLING) { - reportDragStart(true /* recatch */); - } - } - if (newState == ScrollState.SETTLING) { - reportDragEnd(); - } - - mState = newState; - } - - public boolean isDraggingOrSettling() { - return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING; - } - - public int getDownX() { - return (int) mDownPos.x; - } - - public int getDownY() { - return (int) mDownPos.y; - } - /** - * There's no touch and there's no animation. - */ - public boolean isIdleState() { - return mState == ScrollState.IDLE; - } - - public boolean isSettlingState() { - return mState == ScrollState.SETTLING; - } - - public boolean isDraggingState() { - return mState == ScrollState.DRAGGING; - } - - private final PointF mDownPos = new PointF(); - private final PointF mLastPos = new PointF(); - private final Direction mDir; - private final boolean mIsRtl; - - private final float mTouchSlop; - private final float mMaxVelocity; - - /* Client of this gesture detector can register a callback. */ - private final Listener mListener; - - private VelocityTracker mVelocityTracker; - - private float mLastDisplacement; - private float mDisplacement; - - private float mSubtractDisplacement; - private boolean mIgnoreSlopWhenSettling; - - public interface Listener { - void onDragStart(boolean start); - - boolean onDrag(float displacement); - - default boolean onDrag(float displacement, MotionEvent event) { - return onDrag(displacement); - } - - void onDragEnd(float velocity, boolean fling); - } - - public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) { - this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources())); - } - - @VisibleForTesting - protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, - @NonNull Direction dir, boolean isRtl) { - mListener = l; - mDir = dir; - mIsRtl = isRtl; - mTouchSlop = config.getScaledTouchSlop(); - mMaxVelocity = config.getScaledMaximumFlingVelocity(); - } - - public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { - mScrollConditions = scrollDirectionFlags; - mIgnoreSlopWhenSettling = ignoreSlop; - } - - public int getScrollDirections() { - return mScrollConditions; - } - - private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) { - // reject cases where the angle or slop condition is not met. - if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop) - > Math.abs(mDisplacement)) { - return false; - } - - // Check if the client is interested in scroll in current direction. - if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement)) || - ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement))) { - return true; - } - return false; - } - - public boolean onTouchEvent(MotionEvent ev) { - int actionMasked = ev.getActionMasked(); - if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) { - mVelocityTracker.clear(); - } - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - switch (actionMasked) { - case MotionEvent.ACTION_DOWN: - mActivePointerId = ev.getPointerId(0); - mDownPos.set(ev.getX(), ev.getY()); - mLastPos.set(mDownPos); - mLastDisplacement = 0; - mDisplacement = 0; - - if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { - setState(ScrollState.DRAGGING); - } - break; - //case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_POINTER_UP: - int ptrIdx = ev.getActionIndex(); - int ptrId = ev.getPointerId(ptrIdx); - if (ptrId == mActivePointerId) { - final int newPointerIdx = ptrIdx == 0 ? 1 : 0; - mDownPos.set( - ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), - ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); - mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); - mActivePointerId = ev.getPointerId(newPointerIdx); - } - break; - case MotionEvent.ACTION_MOVE: - int pointerIndex = ev.findPointerIndex(mActivePointerId); - if (pointerIndex == INVALID_POINTER_ID) { - break; - } - mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos, mIsRtl); - - // handle state and listener calls. - if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) { - setState(ScrollState.DRAGGING); - } - if (mState == ScrollState.DRAGGING) { - reportDragging(ev); - } - mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - // These are synthetic events and there is no need to update internal values. - if (mState == ScrollState.DRAGGING) { - setState(ScrollState.SETTLING); - } - mVelocityTracker.recycle(); - mVelocityTracker = null; - break; - default: - break; - } - return true; - } - - public void finishedScrolling() { - setState(ScrollState.IDLE); - } - - private boolean reportDragStart(boolean recatch) { - mListener.onDragStart(!recatch); - if (DBG) { - Log.d(TAG, "onDragStart recatch:" + recatch); - } - return true; - } - - private void initializeDragging() { - if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { - mSubtractDisplacement = 0; - } - if (mDisplacement > 0) { - mSubtractDisplacement = mTouchSlop; - } else { - mSubtractDisplacement = -mTouchSlop; - } - } - - /** - * Returns if the start drag was towards the positive direction or negative. - * - * @see #setDetectableScrollConditions(int, boolean) - * @see #DIRECTION_BOTH - */ - public boolean wasInitialTouchPositive() { - return mDir.isPositive(mSubtractDisplacement); - } - - private boolean reportDragging(MotionEvent event) { - if (mDisplacement != mLastDisplacement) { - if (DBG) { - Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement)); - } - - mLastDisplacement = mDisplacement; - return mListener.onDrag(mDisplacement - mSubtractDisplacement, event); - } - return true; - } - - private void reportDragEnd() { - mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); - float velocity = mDir.getVelocity(mVelocityTracker, mIsRtl) / 1000; - if (DBG) { - Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f", - mDisplacement, velocity)); - } - - mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS); - } - - public static long calculateDuration(float velocity, float progressNeeded) { - // TODO: make these values constants after tuning. - float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity)); - float travelDistance = Math.max(0.2f, progressNeeded); - long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance); - if (DBG) { - Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded)); - } - return duration; - } -} diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java index 12d35e962..0f8152057 100644 --- a/src/com/android/launcher3/util/ConfigMonitor.java +++ b/src/com/android/launcher3/util/ConfigMonitor.java @@ -16,20 +16,15 @@ package com.android.launcher3.util; * limitations under the License. */ +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Point; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManager.DisplayListener; -import android.os.Handler; import android.util.Log; -import android.view.Display; -import android.view.WindowManager; - -import com.android.launcher3.MainThreadExecutor; import java.util.function.Consumer; @@ -37,7 +32,8 @@ import java.util.function.Consumer; * {@link BroadcastReceiver} which watches configuration changes and * notifies the callback in case changes which affect the device profile occur. */ -public class ConfigMonitor extends BroadcastReceiver implements DisplayListener { +public class ConfigMonitor extends BroadcastReceiver implements + DefaultDisplay.DisplayInfoChangeListener { private static final String TAG = "ConfigMonitor"; @@ -61,24 +57,19 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener mFontScale = config.fontScale; mDensity = config.densityDpi; - Display display = getDefaultDisplay(context); - mDisplayId = display.getDisplayId(); - - mRealSize = new Point(); - display.getRealSize(mRealSize); + DefaultDisplay display = DefaultDisplay.INSTANCE.get(context); + display.addChangeListener(this); + DefaultDisplay.Info displayInfo = display.getInfo(); + mDisplayId = displayInfo.id; - mSmallestSize = new Point(); - mLargestSize = new Point(); - display.getCurrentSizeRange(mSmallestSize, mLargestSize); + mRealSize = new Point(displayInfo.realSize); + mSmallestSize = new Point(displayInfo.smallestSize); + mLargestSize = new Point(displayInfo.largestSize); mCallback = callback; // Listen for configuration change mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); - - // Listen for display manager change - mContext.getSystemService(DisplayManager.class) - .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper())); } @Override @@ -91,26 +82,19 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener } @Override - public void onDisplayAdded(int displayId) { } - - @Override - public void onDisplayRemoved(int displayId) { } - - @Override - public void onDisplayChanged(int displayId) { - if (displayId != mDisplayId) { + public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) { + if (info.id != mDisplayId) { return; } - Display display = getDefaultDisplay(mContext); - display.getRealSize(mTmpPoint1); - + mTmpPoint1.set(info.realSize.x, info.realSize.y); if (!mRealSize.equals(mTmpPoint1) && !mRealSize.equals(mTmpPoint1.y, mTmpPoint1.x)) { Log.d(TAG, String.format("Display size changed from %s to %s", mRealSize, mTmpPoint1)); notifyChange(); return; } - display.getCurrentSizeRange(mTmpPoint1, mTmpPoint2); + mTmpPoint1.set(info.smallestSize.x, info.smallestSize.y); + mTmpPoint2.set(info.largestSize.x, info.largestSize.y); if (!mSmallestSize.equals(mTmpPoint1) || !mLargestSize.equals(mTmpPoint2)) { Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]", mSmallestSize, mLargestSize, mTmpPoint1, mTmpPoint2)); @@ -122,18 +106,15 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener if (mCallback != null) { Consumer<Context> callback = mCallback; mCallback = null; - new MainThreadExecutor().execute(() -> callback.accept(mContext)); + MAIN_EXECUTOR.execute(() -> callback.accept(mContext)); } } - private Display getDefaultDisplay(Context context) { - return context.getSystemService(WindowManager.class).getDefaultDisplay(); - } - public void unregister() { try { mContext.unregisterReceiver(this); - mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext); + display.removeChangeListener(this); } catch (Exception e) { Log.e(TAG, "Failed to unregister config monitor", e); } diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java index 7719f084d..8529d50ca 100644 --- a/src/com/android/launcher3/util/DefaultDisplay.java +++ b/src/com/android/launcher3/util/DefaultDisplay.java @@ -15,12 +15,15 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.Context; import android.graphics.Point; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Handler; import android.os.Message; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.WindowManager; @@ -54,7 +57,7 @@ public class DefaultDisplay implements DisplayListener { mChangeHandler = new Handler(this::onChange); context.getSystemService(DisplayManager.class) - .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper())); + .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler()); } @Override @@ -122,6 +125,8 @@ public class DefaultDisplay implements DisplayListener { public final Point smallestSize; public final Point largestSize; + public final DisplayMetrics metrics; + private Info(Context context) { Display display = context.getSystemService(WindowManager.class).getDefaultDisplay(); @@ -136,6 +141,9 @@ public class DefaultDisplay implements DisplayListener { largestSize = new Point(); display.getRealSize(realSize); display.getCurrentSizeRange(smallestSize, largestSize); + + metrics = new DisplayMetrics(); + display.getMetrics(metrics); } private boolean hasDifferentSize(Info info) { diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java new file mode 100644 index 000000000..4d5ee49e8 --- /dev/null +++ b/src/com/android/launcher3/util/Executors.java @@ -0,0 +1,87 @@ +/* + * 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.util; + +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; + +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Various different executors used in Launcher + */ +public class Executors { + + // These values are same as that in {@link AsyncTask}. + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CORE_POOL_SIZE = CPU_COUNT + 1; + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + private static final int KEEP_ALIVE = 1; + + /** + * An {@link Executor} to be used with async task with no limit on the queue size. + */ + public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( + CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, + TimeUnit.SECONDS, new LinkedBlockingQueue<>()); + + /** + * Returns the executor for running tasks on the main thread. + */ + public static final LooperExecutor MAIN_EXECUTOR = + new LooperExecutor(Looper.getMainLooper()); + + /** + * A background executor for using time sensitive actions where user is waiting for response. + */ + public static final LooperExecutor UI_HELPER_EXECUTOR = + new LooperExecutor(createAndStartNewForegroundLooper("UiThreadHelper")); + + /** + * Utility method to get a started handler thread statically + */ + public static Looper createAndStartNewLooper(String name) { + return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT); + } + + /** + * Utility method to get a started handler thread statically with the provided priority + */ + public static Looper createAndStartNewLooper(String name, int priority) { + HandlerThread thread = new HandlerThread(name, priority); + thread.start(); + return thread.getLooper(); + } + + /** + * Similar to {@link #createAndStartNewLooper(String)}, but starts the thread with + * foreground priority. + * Think before using + */ + public static Looper createAndStartNewForegroundLooper(String name) { + return createAndStartNewLooper(name, Process.THREAD_PRIORITY_FOREGROUND); + } + + /** + * Executor used for running Launcher model related tasks (eg loading icons or updated db) + */ + public static final LooperExecutor MODEL_EXECUTOR = + new LooperExecutor(createAndStartNewLooper("launcher-loader")); +} diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java index f95f74d60..4a4a5ca22 100644 --- a/src/com/android/launcher3/util/IOUtils.java +++ b/src/com/android/launcher3/util/IOUtils.java @@ -17,10 +17,13 @@ package com.android.launcher3.util; import android.content.Context; +import android.util.Log; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -35,6 +38,7 @@ import java.util.UUID; public class IOUtils { private static final int BUF_SIZE = 0x1000; // 4K + private static final String TAG = "IOUtils"; public static byte[] toByteArray(File file) throws IOException { try (InputStream in = new FileInputStream(file)) { @@ -77,4 +81,16 @@ public class IOUtils { } return file.getAbsolutePath(); } + + public static void closeSilently(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException e) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { + Log.d(TAG, "Error closing", e); + } + } + } + } } diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java index d2a551f45..7252f7ac1 100644 --- a/src/com/android/launcher3/util/IntArray.java +++ b/src/com/android/launcher3/util/IntArray.java @@ -17,6 +17,7 @@ package com.android.launcher3.util; import java.util.Arrays; +import java.util.StringTokenizer; /** * Copy of the platform hidden implementation of android.util.IntArray. @@ -248,6 +249,17 @@ public class IntArray implements Cloneable { return b.toString(); } + public static IntArray fromConcatString(String concatString) { + StringTokenizer tokenizer = new StringTokenizer(concatString, ","); + int[] array = new int[tokenizer.countTokens()]; + int count = 0; + while (tokenizer.hasMoreTokens()) { + array[count] = Integer.parseInt(tokenizer.nextToken().trim()); + count++; + } + return new IntArray(array, array.length); + } + /** * Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds. * diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java index cc0746997..8ac600f73 100644 --- a/src/com/android/launcher3/util/LooperExecutor.java +++ b/src/com/android/launcher3/util/LooperExecutor.java @@ -16,7 +16,9 @@ package com.android.launcher3.util; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; +import android.os.Process; import java.util.List; import java.util.concurrent.AbstractExecutorService; @@ -47,6 +49,13 @@ public class LooperExecutor extends AbstractExecutorService { } /** + * Same as execute, but never runs the action inline. + */ + public void post(Runnable runnable) { + mHandler.post(runnable); + } + + /** * Not supported and throws an exception when used. */ @Override @@ -79,7 +88,31 @@ public class LooperExecutor extends AbstractExecutorService { */ @Override @Deprecated - public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException { + public boolean awaitTermination(long l, TimeUnit timeUnit) { throw new UnsupportedOperationException(); } + + /** + * Returns the thread for this executor + */ + public Thread getThread() { + return mHandler.getLooper().getThread(); + } + + /** + * Returns the looper for this executor + */ + public Looper getLooper() { + return mHandler.getLooper(); + } + + /** + * Set the priority of a thread, based on Linux priorities. + * @param priority Linux priority level, from -20 for highest scheduling priority + * to 19 for lowest scheduling priority. + * @see Process#setThreadPriority(int, int) + */ + public void setThreadPriority(int priority) { + Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority); + } } diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java index e185a3199..fe9c2c468 100644 --- a/src/com/android/launcher3/util/MainThreadInitializedObject.java +++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java @@ -15,12 +15,13 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.Context; import android.os.Looper; import androidx.annotation.VisibleForTesting; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.util.ResourceBasedOverride.Overrides; import java.util.concurrent.ExecutionException; @@ -43,7 +44,7 @@ public class MainThreadInitializedObject<T> { mValue = mProvider.get(context.getApplicationContext()); } else { try { - return new MainThreadExecutor().submit(() -> get(context)).get(); + return MAIN_EXECUTOR.submit(() -> get(context)).get(); } catch (InterruptedException|ExecutionException e) { throw new RuntimeException(e); } diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 7d3a94162..e97adb577 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -24,9 +24,11 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.graphics.Rect; import android.net.Uri; import android.os.Build; @@ -35,6 +37,7 @@ import android.os.PatternMatcher; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.widget.Toast; import com.android.launcher3.AppInfo; @@ -220,4 +223,76 @@ public class PackageManagerHelper { packageFilter.addDataSchemeSpecificPart(pkg, PatternMatcher.PATTERN_LITERAL); return packageFilter; } + + public static boolean isSystemApp(Context context, Intent intent) { + PackageManager pm = context.getPackageManager(); + ComponentName cn = intent.getComponent(); + String packageName = null; + if (cn == null) { + ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + if ((info != null) && (info.activityInfo != null)) { + packageName = info.activityInfo.packageName; + } + } else { + packageName = cn.getPackageName(); + } + if (packageName == null) { + packageName = intent.getPackage(); + } + if (packageName != null) { + try { + PackageInfo info = pm.getPackageInfo(packageName, 0); + return (info != null) && (info.applicationInfo != null) && + ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + } catch (NameNotFoundException e) { + return false; + } + } else { + return false; + } + } + + /** + * Finds a system apk which had a broadcast receiver listening to a particular action. + * @param action intent action used to find the apk + * @return a pair of apk package name and the resources. + */ + public static Pair<String, Resources> findSystemApk(String action, PackageManager pm) { + final Intent intent = new Intent(action); + for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) { + if (info.activityInfo != null && + (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + final String packageName = info.activityInfo.packageName; + try { + final Resources res = pm.getResourcesForApplication(packageName); + return Pair.create(packageName, res); + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + packageName); + } + } + } + return null; + } + + /** + * Returns true if the intent is a valid launch intent for a launcher activity of an app. + * This is used to identify shortcuts which are different from the ones exposed by the + * applications' manifest file. + * + * @param launchIntent The intent that will be launched when the shortcut is clicked. + */ + public static boolean isLauncherAppTarget(Intent launchIntent) { + if (launchIntent != null + && Intent.ACTION_MAIN.equals(launchIntent.getAction()) + && launchIntent.getComponent() != null + && launchIntent.getCategories() != null + && launchIntent.getCategories().size() == 1 + && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER) + && TextUtils.isEmpty(launchIntent.getDataString())) { + // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE. + Bundle extras = launchIntent.getExtras(); + return extras == null || extras.keySet().isEmpty(); + } + return false; + } } diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java index aa11968e4..f243ca628 100644 --- a/src/com/android/launcher3/util/PackageUserKey.java +++ b/src/com/android/launcher3/util/PackageUserKey.java @@ -6,7 +6,6 @@ import android.service.notification.StatusBarNotification; import androidx.annotation.Nullable; import com.android.launcher3.ItemInfo; -import com.android.launcher3.shortcuts.DeepShortcutManager; import java.util.Arrays; diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java index 7ab0d3103..ed66422be 100644 --- a/src/com/android/launcher3/util/Preconditions.java +++ b/src/com/android/launcher3/util/Preconditions.java @@ -16,9 +16,10 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.os.Looper; -import com.android.launcher3.LauncherModel; import com.android.launcher3.config.FeatureFlags; /** @@ -33,7 +34,7 @@ public class Preconditions { } public static void assertWorkerThread() { - if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) { + if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) { throw new IllegalStateException(); } } diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/util/SafeCloseable.java index 509468233..ba8ee04d2 100644 --- a/src/com/android/launcher3/MainThreadExecutor.java +++ b/src/com/android/launcher3/util/SafeCloseable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2019 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. @@ -11,23 +11,16 @@ * 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 + * limitations under the License. */ -package com.android.launcher3; - -import android.os.Looper; - -import com.android.launcher3.util.LooperExecutor; +package com.android.launcher3.util; /** - * An executor service that executes its tasks on the main thread. - * - * Shutting down this executor is not supported. + * Extension of closeable which does not throw an exception */ -public class MainThreadExecutor extends LooperExecutor { +public interface SafeCloseable extends AutoCloseable { - public MainThreadExecutor() { - super(Looper.getMainLooper()); - } + @Override + void close(); } diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java index cc442f988..f8d163230 100644 --- a/src/com/android/launcher3/util/UiThreadHelper.java +++ b/src/com/android/launcher3/util/UiThreadHelper.java @@ -15,14 +15,13 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.app.Activity; import android.content.Context; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.Looper; import android.os.Message; -import android.os.Process; import android.view.inputmethod.InputMethodManager; /** @@ -30,25 +29,15 @@ import android.view.inputmethod.InputMethodManager; */ public class UiThreadHelper { - private static HandlerThread sHandlerThread; private static Handler sHandler; private static final int MSG_HIDE_KEYBOARD = 1; private static final int MSG_SET_ORIENTATION = 2; private static final int MSG_RUN_COMMAND = 3; - public static Looper getBackgroundLooper() { - if (sHandlerThread == null) { - sHandlerThread = - new HandlerThread("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND); - sHandlerThread.start(); - } - return sHandlerThread.getLooper(); - } - private static Handler getHandler(Context context) { if (sHandler == null) { - sHandler = new Handler(getBackgroundLooper(), + sHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), new UiCallbacks(context.getApplicationContext())); } return sHandler; diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java new file mode 100644 index 000000000..04741a165 --- /dev/null +++ b/src/com/android/launcher3/util/VibratorWrapper.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2019 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 static android.os.VibrationEffect.createPredefined; +import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; + +/** + * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary. + */ +@TargetApi(Build.VERSION_CODES.Q) +public class VibratorWrapper { + + public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE = + new MainThreadInitializedObject<>(VibratorWrapper::new); + + private static final VibrationEffect EFFECT_CLICK = + createPredefined(VibrationEffect.EFFECT_CLICK); + + /** + * Haptic when entering overview. + */ + public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK; + + private final Vibrator mVibrator; + private final boolean mHasVibrator; + + private boolean mIsHapticFeedbackEnabled; + + public VibratorWrapper(Context context) { + mVibrator = context.getSystemService(Vibrator.class); + mHasVibrator = mVibrator.hasVibrator(); + if (mHasVibrator) { + final ContentResolver resolver = context.getContentResolver(); + mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver); + final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) { + @Override + public void onChange(boolean selfChange) { + mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver); + } + }; + resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED), + false /* notifyForDescendents */, observer); + } else { + mIsHapticFeedbackEnabled = false; + } + } + + private boolean isHapticFeedbackEnabled(ContentResolver resolver) { + return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1; + } + + /** Vibrates with the given effect if haptic feedback is available and enabled. */ + public void vibrate(VibrationEffect vibrationEffect) { + if (mHasVibrator && mIsHapticFeedbackEnabled) { + UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect)); + } + } +} diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java index acce30860..5a131c83f 100644 --- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java +++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java @@ -16,13 +16,14 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.os.Process; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewTreeObserver.OnDrawListener; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import java.util.ArrayList; import java.util.concurrent.Executor; @@ -54,7 +55,9 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, mLoadAnimationCompleted = true; } - attachObserver(); + if (mAttachedView.isAttachedToWindow()) { + attachObserver(); + } } private void attachObserver() { @@ -66,7 +69,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, @Override public void execute(Runnable command) { mTasks.add(command); - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } @Override @@ -108,7 +111,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, if (mLauncher != null) { mLauncher.clearPendingExecutor(this); } - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_DEFAULT); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } protected boolean isCompleted() { diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java index 8af048d9d..5b33f1849 100644 --- a/src/com/android/launcher3/util/ViewPool.java +++ b/src/com/android/launcher3/util/ViewPool.java @@ -21,12 +21,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.android.launcher3.util.ViewPool.Reusable; - import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import com.android.launcher3.util.ViewPool.Reusable; + /** * Utility class to maintain a pool of reusable views. * During initialization, views are inflated on the background thread. @@ -58,14 +58,18 @@ public class ViewPool<T extends View & Reusable> { Preconditions.assertUIThread(); Handler handler = new Handler(); + // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'. + // Create a different copy to use on the background thread. + LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext()); + // Inflate views on a non looper thread. This allows us to catch errors like calling // "new Handler()" in constructor easily. new Thread(() -> { for (int i = 0; i < initialSize; i++) { - T view = inflateNewView(); + T view = inflateNewView(inflater); handler.post(() -> addToPool(view)); } - }).start(); + }, "ViewPool-init").start(); } @UiThread @@ -94,12 +98,12 @@ public class ViewPool<T extends View & Reusable> { mCurrentSize--; return (T) mPool[mCurrentSize]; } - return inflateNewView(); + return inflateNewView(mInflater); } @AnyThread - private T inflateNewView() { - return (T) mInflater.inflate(mLayoutId, mParent, false); + private T inflateNewView(LayoutInflater inflater) { + return (T) inflater.inflate(mLayoutId, mParent, false); } /** diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java index 5c2468740..2ad80cf66 100644 --- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java +++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java @@ -1,5 +1,7 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.app.WallpaperManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -198,7 +200,7 @@ public class WallpaperOffsetInterpolator extends BroadcastReceiver { private float mOffsetX; public OffsetHandler(Context context) { - super(UiThreadHelper.getBackgroundLooper()); + super(UI_HELPER_EXECUTOR.getLooper()); mInterpolator = Interpolators.DEACCEL_1_5; mWM = WallpaperManager.getInstance(context); } diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index a4518bae3..195a77ad5 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -32,13 +32,14 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; /** * Extension of AbstractFloatingView with common methods for sliding in from bottom */ public abstract class AbstractSlideInView extends AbstractFloatingView - implements SwipeDetector.Listener { + implements SingleAxisSwipeDetector.Listener { protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT = new Property<AbstractSlideInView, Float>(Float.class, "translationShift") { @@ -57,7 +58,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView protected static final float TRANSLATION_SHIFT_OPENED = 0f; protected final Launcher mLauncher; - protected final SwipeDetector mSwipeDetector; + protected final SingleAxisSwipeDetector mSwipeDetector; protected final ObjectAnimator mOpenCloseAnimator; protected View mContent; @@ -73,7 +74,8 @@ public abstract class AbstractSlideInView extends AbstractFloatingView mLauncher = Launcher.getLauncher(context); mScrollInterpolator = Interpolators.SCROLL_CUBIC; - mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL); + mSwipeDetector = new SingleAxisSwipeDetector(context, this, + SingleAxisSwipeDetector.VERTICAL); mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this); mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { @@ -97,7 +99,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView } int directionsToDetectScroll = mSwipeDetector.isIdleState() ? - SwipeDetector.DIRECTION_NEGATIVE : 0; + SingleAxisSwipeDetector.DIRECTION_NEGATIVE : 0; mSwipeDetector.setDetectableScrollConditions( directionsToDetectScroll, false); mSwipeDetector.onTouchEvent(ev); @@ -122,7 +124,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView return mIsOpen && mOpenCloseAnimator.isRunning(); } - /* SwipeDetector.Listener */ + /* SingleAxisSwipeDetector.Listener */ @Override public void onDragStart(boolean start) { } @@ -136,17 +138,17 @@ public abstract class AbstractSlideInView extends AbstractFloatingView } @Override - public void onDragEnd(float velocity, boolean fling) { - if ((fling && velocity > 0) || mTranslationShift > 0.5f) { + public void onDragEnd(float velocity) { + if ((mSwipeDetector.isFling(velocity) && velocity > 0) || mTranslationShift > 0.5f) { mScrollInterpolator = scrollInterpolatorForVelocity(velocity); - mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration( + mOpenCloseAnimator.setDuration(BaseSwipeDetector.calculateDuration( velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift)); close(true); } else { mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat( TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED)); mOpenCloseAnimator.setDuration( - SwipeDetector.calculateDuration(velocity, mTranslationShift)) + BaseSwipeDetector.calculateDuration(velocity, mTranslationShift)) .setInterpolator(Interpolators.DEACCEL); mOpenCloseAnimator.start(); } diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index c08b65931..e43fc8a01 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -170,10 +170,8 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> // Only look for controllers if we are not dispatching from gesture area and proxy is // not active mActiveController = findControllerToHandleTouch(ev); - - if (mActiveController != null) return true; } - return false; + return mActiveController != null; } @Override @@ -465,7 +463,7 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> } public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + "DragLayer"); + writer.println(prefix + "DragLayer:"); if (mActiveController != null) { writer.println(prefix + "\tactiveController: " + mActiveController); mActiveController.dump(prefix + "\t", writer); diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index f728a6776..45c0d9097 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -22,6 +22,7 @@ import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -41,7 +42,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.CancellationSignal; -import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -50,11 +50,17 @@ import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.ImageView; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + import com.android.launcher3.BubbleTextView; import com.android.launcher3.InsettableFrameLayout.LayoutParams; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.dragndrop.DragLayer; @@ -66,13 +72,6 @@ import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.DeepShortcutView; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.annotation.WorkerThread; -import androidx.dynamicanimation.animation.FloatPropertyCompat; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - /** * A view that is created to look like another view with the purpose of creating fluid animations. */ @@ -722,7 +721,7 @@ public class FloatingIconView extends View implements @UiThread public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) { IconLoadResult result = new IconLoadResult(info); - new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> { + MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> { RectF position = new RectF(); getLocationBoundsForView(l, v, isOpening, position); getIconResult(l, v, info, position, result); @@ -768,11 +767,6 @@ public class FloatingIconView extends View implements // Match the position of the original view. view.matchPositionOf(launcher, originalView, isOpening, positionOut); - // Must be called after matchPositionOf so that we know what size to load. - if (shouldLoadIcon) { - view.checkIconResult(originalView, isOpening); - } - // We need to add it to the overlay, but keep it invisible until animation starts.. view.setVisibility(INVISIBLE); parent.addView(view); @@ -799,6 +793,14 @@ public class FloatingIconView extends View implements view.finish(dragLayer); } }; + + // Must be called after matchPositionOf so that we know what size to load. + // Must be called after the fastFinish listener and end runnable is created so that + // the icon is not left in a hidden state. + if (shouldLoadIcon) { + view.checkIconResult(originalView, isOpening); + } + return view; } diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index da1df3f89..9f59d7821 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -288,6 +288,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, anim.addUpdateListener((v) -> invalidate(invalidateRegion)); getOverlay().add(drawable); anim.start(); + return true; } return value; } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index dce839f39..f3fd7ca4d 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -44,6 +44,7 @@ import com.android.launcher3.SimpleOnStylusPressListener; import com.android.launcher3.StylusEventHelper; import com.android.launcher3.Utilities; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener; @@ -96,7 +97,7 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView setBackgroundResource(R.drawable.widget_internal_focus_bg); if (Utilities.ATLEAST_OREO) { - setExecutor(Utilities.THREAD_POOL_EXECUTOR); + setExecutor(Executors.THREAD_POOL_EXECUTOR); } if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) { setOnLightBackground(true); @@ -332,12 +333,7 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) { mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance; if (mAutoAdvanceRunnable == null) { - mAutoAdvanceRunnable = new Runnable() { - @Override - public void run() { - runAutoAdvance(); - } - }; + mAutoAdvanceRunnable = this::runAutoAdvance; } handler.removeCallbacks(mAutoAdvanceRunnable); diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index dc4af8caf..6944879be 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -37,6 +37,7 @@ import com.android.launcher3.SimpleOnStylusPressListener; import com.android.launcher3.StylusEventHelper; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.graphics.DrawableFactory; +import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.model.WidgetItem; /** @@ -80,6 +81,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { private Bitmap mDeferredBitmap; protected final BaseActivity mActivity; + protected DeviceProfile mDeviceProfile; public WidgetCell(Context context) { this(context, null); @@ -93,6 +95,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { super(context, attrs, defStyle); mActivity = BaseActivity.fromContext(context); + mDeviceProfile = mActivity.getDeviceProfile(); mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); setContainerWidth(); @@ -102,8 +105,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { } private void setContainerWidth() { - DeviceProfile profile = mActivity.getDeviceProfile(); - mCellSize = (int) (profile.cellWidthPx * WIDTH_SCALE); + mCellSize = (int) (mDeviceProfile.allAppsCellWidthPx * WIDTH_SCALE); mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE); } @@ -180,8 +182,10 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { return; } if (bitmap != null) { - mWidgetImage.setBitmap(bitmap, DrawableFactory.INSTANCE.get(getContext()) - .getBadgeForUser(mItem.user, getContext())); + mWidgetImage.setBitmap(bitmap, + DrawableFactory.INSTANCE.get(getContext()).getBadgeForUser(mItem.user, + getContext(), BaseIconFactory.getBadgeSizeForIconSize( + mDeviceProfile.allAppsIconSizePx))); if (mAnimatePreview) { mWidgetImage.setAlpha(0f); ViewPropertyAnimator anim = mWidgetImage.animate(); diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java new file mode 100644 index 000000000..f20c83d92 --- /dev/null +++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.widget.custom; + +import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.os.Parcel; +import android.os.Process; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.LauncherAppWidgetHostView; +import com.android.systemui.plugins.CustomWidgetPlugin; +import com.android.systemui.plugins.PluginListener; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * CustomWidgetManager handles custom widgets implemented as a plugin. + */ +public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> { + + public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE = + new MainThreadInitializedObject<>(CustomWidgetManager::new); + + /** + * auto provider Id is an ever-increasing number that serves as the providerId whenever a new + * custom widget has been connected. + */ + private int mAutoProviderId = 0; + private final SparseArray<CustomWidgetPlugin> mPlugins; + private final SparseArray<WeakReference<Context>> mContexts; + private final List<CustomAppWidgetProviderInfo> mCustomWidgets; + private final SparseArray<ComponentName> mWidgetsIdMap; + private Consumer<PackageUserKey> mWidgetRefreshCallback; + + private CustomWidgetManager(Context context) { + mPlugins = new SparseArray<>(); + mContexts = new SparseArray<>(); + mCustomWidgets = new ArrayList<>(); + mWidgetsIdMap = new SparseArray<>(); + PluginManagerWrapper.INSTANCE.get(context) + .addPluginListener(this, CustomWidgetPlugin.class, true); + } + + @Override + public void onPluginConnected(CustomWidgetPlugin plugin, Context context) { + mPlugins.put(mAutoProviderId, plugin); + mContexts.put(mAutoProviderId, new WeakReference<>(context)); + List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context) + .getInstalledProvidersForProfile(Process.myUserHandle()); + if (providers.isEmpty()) return; + Parcel parcel = Parcel.obtain(); + providers.get(0).writeToParcel(parcel, 0); + parcel.setDataPosition(0); + CustomAppWidgetProviderInfo info = newInfo(mAutoProviderId, plugin, parcel, context); + parcel.recycle(); + mCustomWidgets.add(info); + mWidgetsIdMap.put(mAutoProviderId, info.provider); + mWidgetRefreshCallback.accept(null); + mAutoProviderId++; + } + + @Override + public void onPluginDisconnected(CustomWidgetPlugin plugin) { + int providerId = findProviderId(plugin); + if (providerId == -1) return; + mPlugins.remove(providerId); + mContexts.remove(providerId); + mCustomWidgets.remove(getWidgetProvider(providerId)); + mWidgetsIdMap.remove(providerId); + } + + /** + * Inject a callback function to refresh the widgets. + */ + public void setWidgetRefreshCallback(Consumer<PackageUserKey> cb) { + mWidgetRefreshCallback = cb; + } + + /** + * Callback method to inform a plugin it's corresponding widget has been created. + */ + public void onViewCreated(LauncherAppWidgetHostView view) { + CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo(); + CustomWidgetPlugin plugin = mPlugins.get(info.providerId); + WeakReference<Context> context = mContexts.get(info.providerId); + if (plugin == null) return; + plugin.onViewCreated(context == null ? null : context.get(), view); + } + + /** + * Returns the list of custom widgets. + */ + @NonNull + public List<CustomAppWidgetProviderInfo> getCustomWidgets() { + return mCustomWidgets; + } + + /** + * Returns the widget id for a specific provider. + */ + public int getWidgetIdForCustomProvider(@NonNull ComponentName provider) { + int index = mWidgetsIdMap.indexOfValue(provider); + if (index >= 0) { + return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - mWidgetsIdMap.keyAt(index); + } else { + return AppWidgetManager.INVALID_APPWIDGET_ID; + } + } + + /** + * Returns the widget provider in respect to given widget id. + */ + @Nullable + public LauncherAppWidgetProviderInfo getWidgetProvider(int widgetId) { + ComponentName cn = mWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId); + for (LauncherAppWidgetProviderInfo info : mCustomWidgets) { + if (info.provider.equals(cn)) return info; + } + return null; + } + + private static CustomAppWidgetProviderInfo newInfo(int providerId, CustomWidgetPlugin plugin, + Parcel parcel, Context context) { + CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo( + parcel, false, providerId); + info.provider = new ComponentName( + context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId); + + info.label = plugin.getLabel(context); + info.resizeMode = plugin.getResizeMode(context); + + info.spanX = plugin.getSpanX(context); + info.spanY = plugin.getSpanY(context); + info.minSpanX = plugin.getMinSpanX(context); + info.minSpanY = plugin.getMinSpanY(context); + return info; + } + + private int findProviderId(CustomWidgetPlugin plugin) { + for (int i = 0; i < mPlugins.size(); i++) { + int providerId = mPlugins.keyAt(i); + if (mPlugins.get(providerId) == plugin) { + return providerId; + } + } + return -1; + } +} diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java b/src/com/android/launcher3/widget/custom/CustomWidgetParser.java deleted file mode 100644 index 00720c449..000000000 --- a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.widget.custom; - -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProviderInfo; -import android.content.ComponentName; -import android.content.Context; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.os.Parcel; -import android.os.Process; -import android.util.SparseArray; -import android.util.Xml; - -import com.android.launcher3.LauncherAppWidgetInfo; -import com.android.launcher3.LauncherAppWidgetProviderInfo; -import com.android.launcher3.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX; - -/** - * Utility class to parse {@ink CustomAppWidgetProviderInfo} definitions from xml - */ -public class CustomWidgetParser { - - private static List<LauncherAppWidgetProviderInfo> sCustomWidgets; - private static SparseArray<ComponentName> sWidgetsIdMap; - - public static List<LauncherAppWidgetProviderInfo> getCustomWidgets(Context context) { - if (sCustomWidgets == null) { - // Synchronization not needed as it it safe to load multiple times - parseCustomWidgets(context); - } - - return sCustomWidgets; - } - - public static int getWidgetIdForCustomProvider(Context context, ComponentName provider) { - if (sWidgetsIdMap == null) { - parseCustomWidgets(context); - } - int index = sWidgetsIdMap.indexOfValue(provider); - if (index >= 0) { - return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - sWidgetsIdMap.keyAt(index); - } else { - return AppWidgetManager.INVALID_APPWIDGET_ID; - } - } - - public static LauncherAppWidgetProviderInfo getWidgetProvider(Context context, int widgetId) { - if (sWidgetsIdMap == null || sCustomWidgets == null) { - parseCustomWidgets(context); - } - ComponentName cn = sWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId); - for (LauncherAppWidgetProviderInfo info : sCustomWidgets) { - if (info.provider.equals(cn)) { - return info; - } - } - return null; - } - - private static void parseCustomWidgets(Context context) { - ArrayList<LauncherAppWidgetProviderInfo> widgets = new ArrayList<>(); - SparseArray<ComponentName> idMap = new SparseArray<>(); - - List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context) - .getInstalledProvidersForProfile(Process.myUserHandle()); - if (providers.isEmpty()) { - sCustomWidgets = widgets; - sWidgetsIdMap = idMap; - return; - } - - Parcel parcel = Parcel.obtain(); - providers.get(0).writeToParcel(parcel, 0); - - try (XmlResourceParser parser = context.getResources().getXml(R.xml.custom_widgets)) { - final int depth = parser.getDepth(); - int type; - - while (((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - if ((type == XmlPullParser.START_TAG) && "widget".equals(parser.getName())) { - TypedArray a = context.obtainStyledAttributes( - Xml.asAttributeSet(parser), R.styleable.CustomAppWidgetProviderInfo); - - parcel.setDataPosition(0); - CustomAppWidgetProviderInfo info = newInfo(a, parcel, context); - widgets.add(info); - a.recycle(); - - idMap.put(info.providerId, info.provider); - } - } - } catch (IOException | XmlPullParserException e) { - throw new RuntimeException(e); - } - parcel.recycle(); - sCustomWidgets = widgets; - sWidgetsIdMap = idMap; - } - - private static CustomAppWidgetProviderInfo newInfo(TypedArray a, Parcel parcel, Context context) { - int providerId = a.getInt(R.styleable.CustomAppWidgetProviderInfo_providerId, 0); - CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false, providerId); - info.provider = new ComponentName(context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId); - - info.label = a.getString(R.styleable.CustomAppWidgetProviderInfo_android_label); - info.initialLayout = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_initialLayout, 0); - info.icon = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_icon, 0); - info.previewImage = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_previewImage, 0); - info.resizeMode = a.getInt(R.styleable.CustomAppWidgetProviderInfo_android_resizeMode, 0); - - info.spanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numColumns, 1); - info.spanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numRows, 1); - info.minSpanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinColumns, 1); - info.minSpanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinRows, 1); - return info; - } -} |