callbacks) {
- super(app, dataModel, allAppsList, pageToBindFirst, callbacks);
+ AllAppsList allAppsList, Callbacks[] callbacks) {
+ this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
+ }
+
+ public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+ AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
+ super(app, dataModel, allAppsList, callbacks, executor);
}
@Override
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 18f3f9d91c..3b3dc0196c 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -16,11 +16,14 @@
package com.android.launcher3.model;
+import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
-import com.android.launcher3.icons.ComponentWithLabel;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetListRowEntry;
@@ -29,14 +32,16 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
-import androidx.annotation.Nullable;
-
/**
* Widgets data model that is used by the adapters of the widget views and controllers.
*
* The widgets and shortcuts are organized using package name as its index.
*/
public class WidgetsModel {
+
+ // True is the widget support is disabled.
+ public static final boolean GO_DISABLE_WIDGETS = true;
+
private static final ArrayList EMPTY_WIDGET_LIST = new ArrayList<>();
/**
@@ -55,7 +60,7 @@ public class WidgetsModel {
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
*/
- public List update(LauncherAppState app,
+ public List update(LauncherAppState app,
@Nullable PackageUserKey packageUser) {
return Collections.emptyList();
}
@@ -64,4 +69,9 @@ public class WidgetsModel {
public void onPackageIconsUpdated(Set packageNames, UserHandle user,
LauncherAppState app) {
}
+
+ public WidgetItem getWidgetProviderInfoByProviderName(
+ ComponentName providerName) {
+ return null;
+ }
}
\ No newline at end of file
diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
deleted file mode 100644
index 42b1194292..0000000000
--- a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shortcuts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.notification.NotificationKeyData;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
- */
-public class DeepShortcutManager {
-
- private static final DeepShortcutManager sInstance = new DeepShortcutManager();
-
- public static DeepShortcutManager getInstance(Context context) {
- return sInstance;
- }
-
- private final QueryResult mFailure = new QueryResult();
-
- private DeepShortcutManager() { }
-
- /**
- * Queries for the shortcuts with the package name and provided ids.
- *
- * This method is intended to get the full details for shortcuts when they are added or updated,
- * because we only get "key" fields in onShortcutsChanged().
- */
- public QueryResult queryForFullDetails(String packageName,
- List shortcutIds, UserHandle user) {
- return mFailure;
- }
-
- /**
- * Gets all the manifest and dynamic shortcuts associated with the given package and user,
- * to be displayed in the shortcuts container on long press.
- */
- public QueryResult queryForShortcutsContainer(ComponentName activity,
- UserHandle user) {
- return mFailure;
- }
-
- /**
- * Removes the given shortcut from the current list of pinned shortcuts.
- * (Runs on background thread)
- */
- public void unpinShortcut(final ShortcutKey key) {
- }
-
- /**
- * Adds the given shortcut to the current list of pinned shortcuts.
- * (Runs on background thread)
- */
- public void pinShortcut(final ShortcutKey key) {
- }
-
- public void startShortcut(String packageName, String id, Rect sourceBounds,
- Bundle startActivityOptions, UserHandle user) {
- }
-
- public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) {
- return null;
- }
-
- /**
- * Returns the id's of pinned shortcuts associated with the given package and user.
- *
- * If packageName is null, returns all pinned shortcuts regardless of package.
- */
- public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
- return mFailure;
- }
-
- public QueryResult queryForPinnedShortcuts(String packageName,
- List shortcutIds, UserHandle user) {
- return mFailure;
- }
-
- public QueryResult queryForAllShortcuts(UserHandle user) {
- return mFailure;
- }
-
- public boolean hasHostPermission() {
- return false;
- }
-
-
- public static class QueryResult extends ArrayList {
-
- public boolean wasSuccess() {
- return true;
- }
- }
-}
diff --git a/gradle.properties b/gradle.properties
index 5b90f08148..7a51375653 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,12 +2,12 @@
android.useAndroidX = true
android.enableJetifier = true
-ANDROID_X_VERSION=1.0.0-beta01
+ANDROID_X_VERSION=1+
-GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.3.0
+GRADLE_CLASS_PATH=com.android.tools.build:gradle:3.5.1
-PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.6
+PROTOBUF_CLASS_PATH=com.google.protobuf:protobuf-gradle-plugin:0.8.8
PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7
BUILD_TOOLS_VERSION=28.0.3
-COMPILE_SDK=android-Q
\ No newline at end of file
+COMPILE_SDK=android-R
diff --git a/iconloaderlib/Android.bp b/iconloaderlib/Android.bp
deleted file mode 100644
index f12d16e42a..0000000000
--- a/iconloaderlib/Android.bp
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2018 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.
-
-android_library {
- name: "iconloader_base",
- sdk_version: "28",
- min_sdk_version: "21",
- static_libs: [
- "androidx.core_core",
- ],
- resource_dirs: [
- "res",
- ],
- srcs: [
- "src/**/*.java",
- ],
-}
-
-android_library {
- name: "iconloader",
- sdk_version: "system_current",
- min_sdk_version: "21",
- static_libs: [
- "androidx.core_core",
- ],
- resource_dirs: [
- "res",
- ],
- srcs: [
- "src/**/*.java",
- "src_full_lib/**/*.java",
- ],
-}
diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle
deleted file mode 100644
index 8a4d2b76e5..0000000000
--- a/iconloaderlib/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion COMPILE_SDK
- buildToolsVersion BUILD_TOOLS_VERSION
- publishNonDefault true
-
- defaultConfig {
- minSdkVersion 25
- targetSdkVersion 28
- versionCode 1
- versionName "1.0"
- }
-
- sourceSets {
- main {
- java.srcDirs = ['src', 'src_full_lib']
- manifest.srcFile 'AndroidManifest.xml'
- res.srcDirs = ['res']
- }
- }
-
- lintOptions {
- abortOnError false
- }
-
- tasks.withType(JavaCompile) {
- options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- implementation "androidx.core:core:${ANDROID_X_VERSION}"
-}
diff --git a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
deleted file mode 100644
index 9f13cf5719..0000000000
--- a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/iconloaderlib/res/drawable/ic_instant_app_badge.xml b/iconloaderlib/res/drawable/ic_instant_app_badge.xml
deleted file mode 100644
index b74317e5f2..0000000000
--- a/iconloaderlib/res/drawable/ic_instant_app_badge.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/iconloaderlib/res/values/colors.xml b/iconloaderlib/res/values/colors.xml
deleted file mode 100644
index 873b2fc5fb..0000000000
--- a/iconloaderlib/res/values/colors.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- #FFFFFF
-
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
deleted file mode 100644
index 5c4f37ca98..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ /dev/null
@@ -1,365 +0,0 @@
-package com.android.launcher3.icons;
-
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-/**
- * This class will be moved to androidx library. There shouldn't be any dependency outside
- * this package.
- */
-public class BaseIconFactory implements AutoCloseable {
-
- private static final String TAG = "BaseIconFactory";
- private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
- static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
- static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
-
- private static final float ICON_BADGE_SCALE = 0.444f;
-
- private final Rect mOldBounds = new Rect();
- protected final Context mContext;
- private final Canvas mCanvas;
- private final PackageManager mPm;
- private final ColorExtractor mColorExtractor;
- private boolean mDisableColorExtractor;
-
- protected final int mFillResIconDpi;
- protected final int mIconBitmapSize;
-
- private IconNormalizer mNormalizer;
- private ShadowGenerator mShadowGenerator;
- private final boolean mShapeDetection;
-
- private Drawable mWrapperIcon;
- private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
-
- protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
- boolean shapeDetection) {
- mContext = context.getApplicationContext();
- mShapeDetection = shapeDetection;
- mFillResIconDpi = fillResIconDpi;
- mIconBitmapSize = iconBitmapSize;
-
- mPm = mContext.getPackageManager();
- mColorExtractor = new ColorExtractor();
-
- mCanvas = new Canvas();
- mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
- clear();
- }
-
- protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
- this(context, fillResIconDpi, iconBitmapSize, false);
- }
-
- protected void clear() {
- mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
- mDisableColorExtractor = false;
- }
-
- public ShadowGenerator getShadowGenerator() {
- if (mShadowGenerator == null) {
- mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
- }
- return mShadowGenerator;
- }
-
- public IconNormalizer getNormalizer() {
- if (mNormalizer == null) {
- mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
- }
- return mNormalizer;
- }
-
- @SuppressWarnings("deprecation")
- public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
- try {
- Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
- if (resources != null) {
- final int id = resources.getIdentifier(iconRes.resourceName, null, null);
- // do not stamp old legacy shortcuts as the app may have already forgotten about it
- return createBadgedIconBitmap(
- resources.getDrawableForDensity(id, mFillResIconDpi),
- Process.myUserHandle() /* only available on primary user */,
- false /* do not apply legacy treatment */);
- }
- } catch (Exception e) {
- // Icon not found.
- }
- return null;
- }
-
- public BitmapInfo createIconBitmap(Bitmap icon) {
- if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) {
- icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
- }
-
- return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor);
- }
-
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
- boolean shrinkNonAdaptiveIcons) {
- return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
- }
-
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
- int iconAppTargetSdk) {
- return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
- }
-
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
- int iconAppTargetSdk, boolean isInstantApp) {
- return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
- }
-
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
- int iconAppTargetSdk, boolean isInstantApp, float[] scale) {
- boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
- (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
- return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);
- }
-
- public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
- boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
- (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
- return createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);
- }
-
- /**
- * Creates bitmap using the source drawable and various parameters.
- * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
- *
- * @param icon source of the icon
- * @param user info can be used for a badge
- * @param shrinkNonAdaptiveIcons {@code true} if non adaptive icons should be treated
- * @param isInstantApp info can be used for a badge
- * @param scale returns the scale result from normalization
- * @return a bitmap suitable for disaplaying as an icon at various system UIs.
- */
- public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,
- boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
- if (scale == null) {
- scale = new float[1];
- }
- icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
- Bitmap bitmap = createIconBitmap(icon, scale[0]);
- if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
- mCanvas.setBitmap(bitmap);
- getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
- mCanvas.setBitmap(null);
- }
-
- if (isInstantApp) {
- badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
- }
- if (user != null) {
- BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
- Drawable badged = mPm.getUserBadgedIcon(drawable, user);
- if (badged instanceof BitmapDrawable) {
- bitmap = ((BitmapDrawable) badged).getBitmap();
- } else {
- bitmap = createIconBitmap(badged, 1f);
- }
- }
- return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor);
- }
-
- public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
- RectF iconBounds = new RectF();
- float[] scale = new float[1];
- icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
- return createIconBitmap(icon,
- Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
- }
-
- /**
- * Sets the background color used for wrapped adaptive icon
- */
- public void setWrapperBackgroundColor(int color) {
- mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
- }
-
- /**
- * Disables the dominant color extraction for all icons loaded.
- */
- public void disableColorExtraction() {
- mDisableColorExtractor = true;
- }
-
- private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
- boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
- if (icon == null) {
- return null;
- }
- float scale = 1f;
-
- if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
- if (mWrapperIcon == null) {
- mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
- .mutate();
- }
- AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
- dr.setBounds(0, 0, 1, 1);
- boolean[] outShape = new boolean[1];
- scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
- if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
- FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
- fsd.setDrawable(icon);
- fsd.setScale(scale);
- icon = dr;
- scale = getNormalizer().getScale(icon, outIconBounds, null, null);
-
- ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
- }
- } else {
- scale = getNormalizer().getScale(icon, outIconBounds, null, null);
- }
-
- outScale[0] = scale;
- return icon;
- }
-
- /**
- * Adds the {@param badge} on top of {@param target} using the badge dimensions.
- */
- public void badgeWithDrawable(Bitmap target, Drawable badge) {
- mCanvas.setBitmap(target);
- badgeWithDrawable(mCanvas, badge);
- mCanvas.setBitmap(null);
- }
-
- /**
- * Adds the {@param badge} on top of {@param target} using the badge dimensions.
- */
- public void badgeWithDrawable(Canvas target, Drawable badge) {
- int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
- badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
- mIconBitmapSize, mIconBitmapSize);
- badge.draw(target);
- }
-
- private Bitmap createIconBitmap(Drawable icon, float scale) {
- return createIconBitmap(icon, scale, mIconBitmapSize);
- }
-
- /**
- * @param icon drawable that should be flattened to a bitmap
- * @param scale the scale to apply before drawing {@param icon} on the canvas
- */
- public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
- Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- if (icon == null) {
- return bitmap;
- }
- mCanvas.setBitmap(bitmap);
- mOldBounds.set(icon.getBounds());
-
- if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
- int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
- Math.round(size * (1 - scale) / 2 ));
- icon.setBounds(offset, offset, size - offset, size - offset);
- icon.draw(mCanvas);
- } else {
- if (icon instanceof BitmapDrawable) {
- BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
- Bitmap b = bitmapDrawable.getBitmap();
- if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
- bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
- }
- }
- int width = size;
- int height = size;
-
- int intrinsicWidth = icon.getIntrinsicWidth();
- int intrinsicHeight = icon.getIntrinsicHeight();
- if (intrinsicWidth > 0 && intrinsicHeight > 0) {
- // Scale the icon proportionally to the icon dimensions
- final float ratio = (float) intrinsicWidth / intrinsicHeight;
- if (intrinsicWidth > intrinsicHeight) {
- height = (int) (width / ratio);
- } else if (intrinsicHeight > intrinsicWidth) {
- width = (int) (height * ratio);
- }
- }
- final int left = (size - width) / 2;
- final int top = (size - height) / 2;
- icon.setBounds(left, top, left + width, top + height);
- mCanvas.save();
- mCanvas.scale(scale, scale, size / 2, size / 2);
- icon.draw(mCanvas);
- mCanvas.restore();
-
- }
- icon.setBounds(mOldBounds);
- mCanvas.setBitmap(null);
- return bitmap;
- }
-
- @Override
- public void close() {
- clear();
- }
-
- public BitmapInfo makeDefaultIcon(UserHandle user) {
- return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi),
- user, Build.VERSION.SDK_INT);
- }
-
- public static Drawable getFullResDefaultActivityIcon(int iconDpi) {
- return Resources.getSystem().getDrawableForDensity(
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
- iconDpi);
- }
-
- /**
- * Returns the correct badge size given an icon size
- */
- public static int getBadgeSizeForIconSize(int iconSize) {
- return (int) (ICON_BADGE_SCALE * iconSize);
- }
-
- /**
- * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
- * This allows the badging to be done based on the action bitmap size rather than
- * the scaled bitmap size.
- */
- private static class FixedSizeBitmapDrawable extends BitmapDrawable {
-
- public FixedSizeBitmapDrawable(Bitmap bitmap) {
- super(null, bitmap);
- }
-
- @Override
- public int getIntrinsicHeight() {
- return getBitmap().getWidth();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return getBitmap().getWidth();
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
deleted file mode 100644
index 245561ea53..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
+++ /dev/null
@@ -1,49 +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.icons;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-
-public class BitmapInfo {
-
- public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
-
- public Bitmap icon;
- public int color;
-
- public void applyTo(BitmapInfo info) {
- info.icon = icon;
- info.color = color;
- }
-
- public final boolean isLowRes() {
- return LOW_RES_ICON == icon;
- }
-
- public static BitmapInfo fromBitmap(Bitmap bitmap) {
- return fromBitmap(bitmap, null);
- }
-
- public static BitmapInfo fromBitmap(Bitmap bitmap, ColorExtractor dominantColorExtractor) {
- BitmapInfo info = new BitmapInfo();
- info.icon = bitmap;
- info.color = dominantColorExtractor != null
- ? dominantColorExtractor.findDominantColorByHue(bitmap)
- : 0;
- return info;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
deleted file mode 100644
index a66b929efb..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
+++ /dev/null
@@ -1,50 +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.icons;
-
-import android.annotation.TargetApi;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Picture;
-import android.os.Build;
-
-/**
- * Interface representing a bitmap draw operation.
- */
-public interface BitmapRenderer {
-
- boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
-
- static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) {
- Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- renderer.draw(new Canvas(result));
- return result;
- }
-
- @TargetApi(Build.VERSION_CODES.P)
- static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) {
- if (!USE_HARDWARE_BITMAP) {
- return createSoftwareBitmap(width, height, renderer);
- }
-
- Picture picture = new Picture();
- renderer.draw(picture.beginRecording(width, height));
- picture.endRecording();
- return Bitmap.createBitmap(picture);
- }
-
- void draw(Canvas out);
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java b/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
deleted file mode 100644
index 87bda825cc..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
+++ /dev/null
@@ -1,127 +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.icons;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.util.SparseArray;
-import java.util.Arrays;
-
-/**
- * Utility class for extracting colors from a bitmap.
- */
-public class ColorExtractor {
-
- private final int NUM_SAMPLES = 20;
- private final float[] mTmpHsv = new float[3];
- private final float[] mTmpHueScoreHistogram = new float[360];
- private final int[] mTmpPixels = new int[NUM_SAMPLES];
- private final SparseArray mTmpRgbScores = new SparseArray<>();
-
- /**
- * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
- * @param bitmap The bitmap to scan
- */
- public int findDominantColorByHue(Bitmap bitmap) {
- return findDominantColorByHue(bitmap, NUM_SAMPLES);
- }
-
- /**
- * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
- * @param bitmap The bitmap to scan
- */
- public int findDominantColorByHue(Bitmap bitmap, int samples) {
- final int height = bitmap.getHeight();
- final int width = bitmap.getWidth();
- int sampleStride = (int) Math.sqrt((height * width) / samples);
- if (sampleStride < 1) {
- sampleStride = 1;
- }
-
- // This is an out-param, for getting the hsv values for an rgb
- float[] hsv = mTmpHsv;
- Arrays.fill(hsv, 0);
-
- // First get the best hue, by creating a histogram over 360 hue buckets,
- // where each pixel contributes a score weighted by saturation, value, and alpha.
- float[] hueScoreHistogram = mTmpHueScoreHistogram;
- Arrays.fill(hueScoreHistogram, 0);
- float highScore = -1;
- int bestHue = -1;
-
- int[] pixels = mTmpPixels;
- Arrays.fill(pixels, 0);
- int pixelCount = 0;
-
- for (int y = 0; y < height; y += sampleStride) {
- for (int x = 0; x < width; x += sampleStride) {
- int argb = bitmap.getPixel(x, y);
- int alpha = 0xFF & (argb >> 24);
- if (alpha < 0x80) {
- // Drop mostly-transparent pixels.
- continue;
- }
- // Remove the alpha channel.
- int rgb = argb | 0xFF000000;
- Color.colorToHSV(rgb, hsv);
- // Bucket colors by the 360 integer hues.
- int hue = (int) hsv[0];
- if (hue < 0 || hue >= hueScoreHistogram.length) {
- // Defensively avoid array bounds violations.
- continue;
- }
- if (pixelCount < samples) {
- pixels[pixelCount++] = rgb;
- }
- float score = hsv[1] * hsv[2];
- hueScoreHistogram[hue] += score;
- if (hueScoreHistogram[hue] > highScore) {
- highScore = hueScoreHistogram[hue];
- bestHue = hue;
- }
- }
- }
-
- SparseArray rgbScores = mTmpRgbScores;
- rgbScores.clear();
- int bestColor = 0xff000000;
- highScore = -1;
- // Go back over the RGB colors that match the winning hue,
- // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
- // The highest-scoring RGB color wins.
- for (int i = 0; i < pixelCount; i++) {
- int rgb = pixels[i];
- Color.colorToHSV(rgb, hsv);
- int hue = (int) hsv[0];
- if (hue == bestHue) {
- float s = hsv[1];
- float v = hsv[2];
- int bucket = (int) (s * 100) + (int) (v * 10000);
- // Score by cumulative saturation * value.
- float score = s * v;
- Float oldTotal = rgbScores.get(bucket);
- float newTotal = oldTotal == null ? score : oldTotal + score;
- rgbScores.put(bucket, newTotal);
- if (newTotal > highScore) {
- highScore = newTotal;
- // All the colors in the winning bucket are very similar. Last in wins.
- bestColor = rgb;
- }
- }
- }
- return bestColor;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
deleted file mode 100644
index 97a0fd3ffc..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
+++ /dev/null
@@ -1,143 +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.icons;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PathMeasure;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.Log;
-import android.view.ViewDebug;
-
-/**
- * Used to draw a notification dot on top of an icon.
- */
-public class DotRenderer {
-
- private static final String TAG = "DotRenderer";
-
- // The dot size is defined as a percentage of the app icon size.
- private static final float SIZE_PERCENTAGE = 0.228f;
-
- private final float mCircleRadius;
- private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
-
- private final Bitmap mBackgroundWithShadow;
- private final float mBitmapOffset;
-
- // Stores the center x and y position as a percentage (0 to 1) of the icon size
- private final float[] mRightDotPosition;
- private final float[] mLeftDotPosition;
-
- public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
- int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
- ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
- builder.ambientShadowAlpha = 88;
- mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
- mCircleRadius = builder.radius;
-
- mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
-
- // Find the points on the path that are closest to the top left and right corners.
- mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
- mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
- }
-
- private static float[] getPathPoint(Path path, float size, float direction) {
- float halfSize = size / 2;
- // Small delta so that we don't get a zero size triangle
- float delta = 1;
-
- float x = halfSize + direction * halfSize;
- Path trianglePath = new Path();
- trianglePath.moveTo(halfSize, halfSize);
- trianglePath.lineTo(x + delta * direction, 0);
- trianglePath.lineTo(x, -delta);
- trianglePath.close();
-
- trianglePath.op(path, Path.Op.INTERSECT);
- float[] pos = new float[2];
- new PathMeasure(trianglePath, false).getPosTan(0, pos, null);
-
- pos[0] = pos[0] / size;
- pos[1] = pos[1] / size;
- return pos;
- }
-
- public float[] getLeftDotPosition() {
- return mLeftDotPosition;
- }
-
- public float[] getRightDotPosition() {
- return mRightDotPosition;
- }
-
- /**
- * Draw a circle on top of the canvas according to the given params.
- */
- public void draw(Canvas canvas, DrawParams params) {
- if (params == null) {
- Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
- return;
- }
- canvas.save();
-
- Rect iconBounds = params.iconBounds;
- float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
- float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
- float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];
-
- // Ensure dot fits entirely in canvas clip bounds.
- Rect canvasBounds = canvas.getClipBounds();
- float offsetX = params.leftAlign
- ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
- : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
- float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));
-
- // We draw the dot relative to its center.
- canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
- canvas.scale(params.scale, params.scale);
-
- mCirclePaint.setColor(Color.BLACK);
- canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
- mCirclePaint.setColor(params.color);
- canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
- canvas.restore();
- }
-
- public static class DrawParams {
- /** The color (possibly based on the icon) to use for the dot. */
- @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
- public int color;
- /** The bounds of the icon that the dot is drawn on top of. */
- @ViewDebug.ExportedProperty(category = "notification dot")
- public Rect iconBounds = new Rect();
- /** The progress of the animation, from 0 to 1. */
- @ViewDebug.ExportedProperty(category = "notification dot")
- public float scale;
- /** Whether the dot should align to the top left of the icon rather than the top right. */
- @ViewDebug.ExportedProperty(category = "notification dot")
- public boolean leftAlign;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
deleted file mode 100644
index 516965ec2b..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/FixedScaleDrawable.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.android.launcher3.icons;
-
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.graphics.Canvas;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.DrawableWrapper;
-import android.util.AttributeSet;
-
-import org.xmlpull.v1.XmlPullParser;
-
-/**
- * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
- */
-public class FixedScaleDrawable extends DrawableWrapper {
-
- // TODO b/33553066 use the constant defined in MaskableIconDrawable
- private static final float LEGACY_ICON_SCALE = .7f * .6667f;
- private float mScaleX, mScaleY;
-
- public FixedScaleDrawable() {
- super(new ColorDrawable());
- mScaleX = LEGACY_ICON_SCALE;
- mScaleY = LEGACY_ICON_SCALE;
- }
-
- @Override
- public void draw(Canvas canvas) {
- int saveCount = canvas.save();
- canvas.scale(mScaleX, mScaleY,
- getBounds().exactCenterX(), getBounds().exactCenterY());
- super.draw(canvas);
- canvas.restoreToCount(saveCount);
- }
-
- @Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
-
- @Override
- public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
-
- public void setScale(float scale) {
- float h = getIntrinsicHeight();
- float w = getIntrinsicWidth();
- mScaleX = scale * LEGACY_ICON_SCALE;
- mScaleY = scale * LEGACY_ICON_SCALE;
- if (h > w && w > 0) {
- mScaleX *= w / h;
- } else if (w > h && h > 0) {
- mScaleY *= h / w;
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
deleted file mode 100644
index 3e818a5568..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons;
-
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.RegionIterator;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-import androidx.annotation.ColorInt;
-
-public class GraphicsUtils {
-
- private static final String TAG = "GraphicsUtils";
-
- /**
- * Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version,
- * it bounds the alpha in valid range instead of throwing an exception to allow for safer
- * interpolation of color animations
- */
- @ColorInt
- public static int setColorAlphaBound(int color, int alpha) {
- if (alpha < 0) {
- alpha = 0;
- } else if (alpha > 255) {
- alpha = 255;
- }
- return (color & 0x00ffffff) | (alpha << 24);
- }
-
- /**
- * Compresses the bitmap to a byte array for serialization.
- */
- public static byte[] flattenBitmap(Bitmap bitmap) {
- // Try go guesstimate how much space the icon will take when serialized
- // to avoid unnecessary allocations/copies during the write (4 bytes per pixel).
- int size = bitmap.getWidth() * bitmap.getHeight() * 4;
- ByteArrayOutputStream out = new ByteArrayOutputStream(size);
- try {
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
- out.flush();
- out.close();
- return out.toByteArray();
- } catch (IOException e) {
- Log.w(TAG, "Could not write bitmap");
- return null;
- }
- }
-
- public static int getArea(Region r) {
- RegionIterator itr = new RegionIterator(r);
- int area = 0;
- Rect tempRect = new Rect();
- while (itr.next(tempRect)) {
- area += tempRect.width() * tempRect.height();
- }
- return area;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
deleted file mode 100644
index de39e79fec..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class IconNormalizer {
-
- private static final String TAG = "IconNormalizer";
- private static final boolean DEBUG = false;
- // Ratio of icon visible area to full icon size for a square shaped icon
- private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
- // Ratio of icon visible area to full icon size for a circular shaped icon
- private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
-
- private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
-
- // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
- private static final float LINEAR_SCALE_SLOPE =
- (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
-
- private static final int MIN_VISIBLE_ALPHA = 40;
-
- // Shape detection related constants
- private static final float BOUND_RATIO_MARGIN = .05f;
- private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
- private static final float SCALE_NOT_INITIALIZED = 0;
-
- // Ratio of the diameter of an normalized circular icon to the actual icon size.
- public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;
-
- private final int mMaxSize;
- private final Bitmap mBitmap;
- private final Canvas mCanvas;
- private final Paint mPaintMaskShape;
- private final Paint mPaintMaskShapeOutline;
- private final byte[] mPixels;
-
- private final RectF mAdaptiveIconBounds;
- private float mAdaptiveIconScale;
-
- private boolean mEnableShapeDetection;
-
- // for each y, stores the position of the leftmost x and the rightmost x
- private final float[] mLeftBorder;
- private final float[] mRightBorder;
- private final Rect mBounds;
- private final Path mShapePath;
- private final Matrix mMatrix;
-
- /** package private **/
- IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) {
- // Use twice the icon size as maximum size to avoid scaling down twice.
- mMaxSize = iconBitmapSize * 2;
- mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
- mCanvas = new Canvas(mBitmap);
- mPixels = new byte[mMaxSize * mMaxSize];
- mLeftBorder = new float[mMaxSize];
- mRightBorder = new float[mMaxSize];
- mBounds = new Rect();
- mAdaptiveIconBounds = new RectF();
-
- mPaintMaskShape = new Paint();
- mPaintMaskShape.setColor(Color.RED);
- mPaintMaskShape.setStyle(Paint.Style.FILL);
- mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
-
- mPaintMaskShapeOutline = new Paint();
- mPaintMaskShapeOutline.setStrokeWidth(
- 2 * context.getResources().getDisplayMetrics().density);
- mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
- mPaintMaskShapeOutline.setColor(Color.BLACK);
- mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-
- mShapePath = new Path();
- mMatrix = new Matrix();
- mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
- mEnableShapeDetection = shapeDetection;
- }
-
- private static float getScale(float hullArea, float boundingArea, float fullArea) {
- float hullByRect = hullArea / boundingArea;
- float scaleRequired;
- if (hullByRect < CIRCLE_AREA_BY_RECT) {
- scaleRequired = MAX_CIRCLE_AREA_FACTOR;
- } else {
- scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
- }
-
- float areaScale = hullArea / fullArea;
- // Use sqrt of the final ratio as the images is scaled across both width and height.
- return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
- }
-
- /**
- * @param d Should be AdaptiveIconDrawable
- * @param size Canvas size to use
- */
- @TargetApi(Build.VERSION_CODES.O)
- public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) {
- Rect tmpBounds = new Rect(d.getBounds());
- d.setBounds(0, 0, size, size);
-
- Path path = ((AdaptiveIconDrawable) d).getIconMask();
- Region region = new Region();
- region.setPath(path, new Region(0, 0, size, size));
-
- Rect hullBounds = region.getBounds();
- int hullArea = GraphicsUtils.getArea(region);
-
- if (outBounds != null) {
- float sizeF = size;
- outBounds.set(
- hullBounds.left / sizeF,
- hullBounds.top / sizeF,
- 1 - (hullBounds.right / sizeF),
- 1 - (hullBounds.bottom / sizeF));
- }
- d.setBounds(tmpBounds);
- return getScale(hullArea, hullArea, size * size);
- }
-
- /**
- * Returns if the shape of the icon is same as the path.
- * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
- */
- private boolean isShape(Path maskPath) {
- // Condition1:
- // If width and height of the path not close to a square, then the icon shape is
- // not same as the mask shape.
- float iconRatio = ((float) mBounds.width()) / mBounds.height();
- if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
- if (DEBUG) {
- Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
- }
- return false;
- }
-
- // Condition 2:
- // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
- // should generate transparent image, if the actual icon is equivalent to the shape.
-
- // Fit the shape within the icon's bounding box
- mMatrix.reset();
- mMatrix.setScale(mBounds.width(), mBounds.height());
- mMatrix.postTranslate(mBounds.left, mBounds.top);
- maskPath.transform(mMatrix, mShapePath);
-
- // XOR operation
- mCanvas.drawPath(mShapePath, mPaintMaskShape);
-
- // DST_OUT operation around the mask path outline
- mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
-
- // Check if the result is almost transparent
- return isTransparentBitmap();
- }
-
- /**
- * Used to determine if certain the bitmap is transparent.
- */
- private boolean isTransparentBitmap() {
- ByteBuffer buffer = ByteBuffer.wrap(mPixels);
- buffer.rewind();
- mBitmap.copyPixelsToBuffer(buffer);
-
- int y = mBounds.top;
- // buffer position
- int index = y * mMaxSize;
- // buffer shift after every row, width of buffer = mMaxSize
- int rowSizeDiff = mMaxSize - mBounds.right;
-
- int sum = 0;
- for (; y < mBounds.bottom; y++) {
- index += mBounds.left;
- for (int x = mBounds.left; x < mBounds.right; x++) {
- if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
- sum++;
- }
- index++;
- }
- index += rowSizeDiff;
- }
-
- float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
- return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
- }
-
- /**
- * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
- * matches the design guidelines for a launcher icon.
- *
- * We first calculate the convex hull of the visible portion of the icon.
- * This hull then compared with the bounding rectangle of the hull to find how closely it
- * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
- * ideal solution but it gives satisfactory result without affecting the performance.
- *
- * This closeness is used to determine the ratio of hull area to the full icon size.
- * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
- *
- * @param outBounds optional rect to receive the fraction distance from each edge.
- */
- public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
- @Nullable Path path, @Nullable boolean[] outMaskShape) {
- if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
- if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
- mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
- }
- if (outBounds != null) {
- outBounds.set(mAdaptiveIconBounds);
- }
- return mAdaptiveIconScale;
- }
- int width = d.getIntrinsicWidth();
- int height = d.getIntrinsicHeight();
- if (width <= 0 || height <= 0) {
- width = width <= 0 || width > mMaxSize ? mMaxSize : width;
- height = height <= 0 || height > mMaxSize ? mMaxSize : height;
- } else if (width > mMaxSize || height > mMaxSize) {
- int max = Math.max(width, height);
- width = mMaxSize * width / max;
- height = mMaxSize * height / max;
- }
-
- mBitmap.eraseColor(Color.TRANSPARENT);
- d.setBounds(0, 0, width, height);
- d.draw(mCanvas);
-
- ByteBuffer buffer = ByteBuffer.wrap(mPixels);
- buffer.rewind();
- mBitmap.copyPixelsToBuffer(buffer);
-
- // Overall bounds of the visible icon.
- int topY = -1;
- int bottomY = -1;
- int leftX = mMaxSize + 1;
- int rightX = -1;
-
- // Create border by going through all pixels one row at a time and for each row find
- // the first and the last non-transparent pixel. Set those values to mLeftBorder and
- // mRightBorder and use -1 if there are no visible pixel in the row.
-
- // buffer position
- int index = 0;
- // buffer shift after every row, width of buffer = mMaxSize
- int rowSizeDiff = mMaxSize - width;
- // first and last position for any row.
- int firstX, lastX;
-
- for (int y = 0; y < height; y++) {
- firstX = lastX = -1;
- for (int x = 0; x < width; x++) {
- if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
- if (firstX == -1) {
- firstX = x;
- }
- lastX = x;
- }
- index++;
- }
- index += rowSizeDiff;
-
- mLeftBorder[y] = firstX;
- mRightBorder[y] = lastX;
-
- // If there is at least one visible pixel, update the overall bounds.
- if (firstX != -1) {
- bottomY = y;
- if (topY == -1) {
- topY = y;
- }
-
- leftX = Math.min(leftX, firstX);
- rightX = Math.max(rightX, lastX);
- }
- }
-
- if (topY == -1 || rightX == -1) {
- // No valid pixels found. Do not scale.
- return 1;
- }
-
- convertToConvexArray(mLeftBorder, 1, topY, bottomY);
- convertToConvexArray(mRightBorder, -1, topY, bottomY);
-
- // Area of the convex hull
- float area = 0;
- for (int y = 0; y < height; y++) {
- if (mLeftBorder[y] <= -1) {
- continue;
- }
- area += mRightBorder[y] - mLeftBorder[y] + 1;
- }
-
- mBounds.left = leftX;
- mBounds.right = rightX;
-
- mBounds.top = topY;
- mBounds.bottom = bottomY;
-
- if (outBounds != null) {
- outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,
- 1 - ((float) mBounds.right) / width,
- 1 - ((float) mBounds.bottom) / height);
- }
- if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) {
- outMaskShape[0] = isShape(path);
- }
- // Area of the rectangle required to fit the convex hull
- float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
- return getScale(area, rectArea, width * height);
- }
-
- /**
- * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
- * (except on either ends) with appropriate values.
- * @param xCoordinates map of x coordinate per y.
- * @param direction 1 for left border and -1 for right border.
- * @param topY the first Y position (inclusive) with a valid value.
- * @param bottomY the last Y position (inclusive) with a valid value.
- */
- private static void convertToConvexArray(
- float[] xCoordinates, int direction, int topY, int bottomY) {
- int total = xCoordinates.length;
- // The tangent at each pixel.
- float[] angles = new float[total - 1];
-
- int first = topY; // First valid y coordinate
- int last = -1; // Last valid y coordinate which didn't have a missing value
-
- float lastAngle = Float.MAX_VALUE;
-
- for (int i = topY + 1; i <= bottomY; i++) {
- if (xCoordinates[i] <= -1) {
- continue;
- }
- int start;
-
- if (lastAngle == Float.MAX_VALUE) {
- start = first;
- } else {
- float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
- start = last;
- // If this position creates a concave angle, keep moving up until we find a
- // position which creates a convex angle.
- if ((currentAngle - lastAngle) * direction < 0) {
- while (start > first) {
- start --;
- currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
- if ((currentAngle - angles[start]) * direction >= 0) {
- break;
- }
- }
- }
- }
-
- // Reset from last check
- lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
- // Update all the points from start.
- for (int j = start; j < i; j++) {
- angles[j] = lastAngle;
- xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
- }
- last = i;
- }
- }
-
- /**
- * @return The diameter of the normalized circle that fits inside of the square (size x size).
- */
- public static int getNormalizedCircleSize(int size) {
- float area = size * size * MAX_CIRCLE_AREA_FACTOR;
- return (int) Math.round(Math.sqrt((4 * area) / Math.PI));
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
deleted file mode 100644
index 5df8043904..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BlurMaskFilter;
-import android.graphics.BlurMaskFilter.Blur;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.RectF;
-
-/**
- * Utility class to add shadows to bitmaps.
- */
-public class ShadowGenerator {
- public static final float BLUR_FACTOR = 0.5f/48;
-
- // Percent of actual icon size
- public static final float KEY_SHADOW_DISTANCE = 1f/48;
- private static final int KEY_SHADOW_ALPHA = 61;
- // Percent of actual icon size
- private static final float HALF_DISTANCE = 0.5f;
- private static final int AMBIENT_SHADOW_ALPHA = 30;
-
- private final int mIconSize;
-
- private final Paint mBlurPaint;
- private final Paint mDrawPaint;
- private final BlurMaskFilter mDefaultBlurMaskFilter;
-
- public ShadowGenerator(int iconSize) {
- mIconSize = iconSize;
- mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
- }
-
- public synchronized void recreateIcon(Bitmap icon, Canvas out) {
- recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
- }
-
- public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
- int ambientAlpha, int keyAlpha, Canvas out) {
- int[] offset = new int[2];
- mBlurPaint.setMaskFilter(blurMaskFilter);
- Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
-
- // Draw ambient shadow
- mDrawPaint.setAlpha(ambientAlpha);
- out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
-
- // Draw key shadow
- mDrawPaint.setAlpha(keyAlpha);
- out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);
-
- // Draw the icon
- mDrawPaint.setAlpha(255);
- out.drawBitmap(icon, 0, 0, mDrawPaint);
- }
-
- /**
- * Returns the minimum amount by which an icon with {@param bounds} should be scaled
- * so that the shadows do not get clipped.
- */
- public static float getScaleForBounds(RectF bounds) {
- float scale = 1;
-
- // For top, left & right, we need same space.
- float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top);
- if (minSide < BLUR_FACTOR) {
- scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide);
- }
-
- float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE;
- if (bounds.bottom < bottomSpace) {
- scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom));
- }
- return scale;
- }
-
- public static class Builder {
-
- public final RectF bounds = new RectF();
- public final int color;
-
- public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA;
-
- public float shadowBlur;
-
- public float keyShadowDistance;
- public int keyShadowAlpha = KEY_SHADOW_ALPHA;
- public float radius;
-
- public Builder(int color) {
- this.color = color;
- }
-
- public Builder setupBlurForSize(int height) {
- shadowBlur = height * 1f / 24;
- keyShadowDistance = height * 1f / 16;
- return this;
- }
-
- public Bitmap createPill(int width, int height) {
- return createPill(width, height, height / 2f);
- }
-
- public Bitmap createPill(int width, int height, float r) {
- radius = r;
-
- int centerX = Math.round(width / 2f + shadowBlur);
- int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
- int center = Math.max(centerX, centerY);
- bounds.set(0, 0, width, height);
- bounds.offsetTo(center - width / 2f, center - height / 2f);
-
- int size = center * 2;
- Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
- drawShadow(new Canvas(result));
- return result;
- }
-
- public void drawShadow(Canvas c) {
- Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- p.setColor(color);
-
- // Key shadow
- p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
- setColorAlphaBound(Color.BLACK, keyShadowAlpha));
- c.drawRoundRect(bounds, radius, radius, p);
-
- // Ambient shadow
- p.setShadowLayer(shadowBlur, 0, 0,
- setColorAlphaBound(Color.BLACK, ambientShadowAlpha));
- c.drawRoundRect(bounds, radius, radius, p);
-
- if (Color.alpha(color) < 255) {
- // Clear any content inside the pill-rect for translucent fill.
- p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
- p.clearShadowLayer();
- p.setColor(Color.BLACK);
- c.drawRoundRect(bounds, radius, radius, p);
-
- p.setXfermode(null);
- p.setColor(color);
- c.drawRoundRect(bounds, radius, radius, p);
- }
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
deleted file mode 100644
index 5cde547ad4..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ /dev/null
@@ -1,576 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons.cache;
-
-import static com.android.launcher3.icons.BaseIconFactory.getFullResDefaultActivityIcon;
-import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
-import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Handler;
-import android.os.LocaleList;
-import android.os.Looper;
-import android.os.Process;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.BitmapRenderer;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SQLiteCacheHelper;
-
-import java.util.AbstractMap;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-
-public abstract class BaseIconCache {
-
- private static final String TAG = "BaseIconCache";
- private static final boolean DEBUG = false;
-
- private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
-
- // Empty class name is used for storing package default entry.
- public static final String EMPTY_CLASS_NAME = ".";
-
- public static class CacheEntry extends BitmapInfo {
- public CharSequence title = "";
- public CharSequence contentDescription = "";
- }
-
- private final HashMap mDefaultIcons = new HashMap<>();
-
- protected final Context mContext;
- protected final PackageManager mPackageManager;
-
- private final Map mCache;
- protected final Handler mWorkerHandler;
-
- protected int mIconDpi;
- protected IconDB mIconDb;
- protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
- protected String mSystemState = "";
-
- private final String mDbFileName;
- private final BitmapFactory.Options mDecodeOptions;
- private final Looper mBgLooper;
-
- public BaseIconCache(Context context, String dbFileName, Looper bgLooper,
- int iconDpi, int iconPixelSize, boolean inMemoryCache) {
- mContext = context;
- mDbFileName = dbFileName;
- mPackageManager = context.getPackageManager();
- mBgLooper = bgLooper;
- mWorkerHandler = new Handler(mBgLooper);
-
- if (inMemoryCache) {
- mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
- } else {
- // Use a placeholder cache
- mCache = new AbstractMap() {
- @Override
- public Set> entrySet() {
- return Collections.emptySet();
- }
-
- @Override
- public CacheEntry put(ComponentKey key, CacheEntry value) {
- return value;
- }
- };
- }
-
- if (BitmapRenderer.USE_HARDWARE_BITMAP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- mDecodeOptions = new BitmapFactory.Options();
- mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
- } else {
- mDecodeOptions = null;
- }
-
- updateSystemState();
- mIconDpi = iconDpi;
- mIconDb = new IconDB(context, dbFileName, iconPixelSize);
- }
-
- /**
- * Returns the persistable serial number for {@param user}. Subclass should implement proper
- * caching strategy to avoid making binder call every time.
- */
- protected abstract long getSerialNumberForUser(UserHandle user);
-
- /**
- * Return true if the given app is an instant app and should be badged appropriately.
- */
- protected abstract boolean isInstantApp(ApplicationInfo info);
-
- /**
- * Opens and returns an icon factory. The factory is recycled by the caller.
- */
- protected abstract BaseIconFactory getIconFactory();
-
- public void updateIconParams(int iconDpi, int iconPixelSize) {
- mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize));
- }
-
- private synchronized void updateIconParamsBg(int iconDpi, int iconPixelSize) {
- mIconDpi = iconDpi;
- mDefaultIcons.clear();
- mIconDb.clear();
- mIconDb.close();
- mIconDb = new IconDB(mContext, mDbFileName, iconPixelSize);
- mCache.clear();
- }
-
- private Drawable getFullResIcon(Resources resources, int iconId) {
- if (resources != null && iconId != 0) {
- try {
- return resources.getDrawableForDensity(iconId, mIconDpi);
- } catch (Resources.NotFoundException e) { }
- }
- return getFullResDefaultActivityIcon(mIconDpi);
- }
-
- public Drawable getFullResIcon(String packageName, int iconId) {
- try {
- return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId);
- } catch (PackageManager.NameNotFoundException e) { }
- return getFullResDefaultActivityIcon(mIconDpi);
- }
-
- public Drawable getFullResIcon(ActivityInfo info) {
- try {
- return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo),
- info.getIconResource());
- } catch (PackageManager.NameNotFoundException e) { }
- return getFullResDefaultActivityIcon(mIconDpi);
- }
-
- private BitmapInfo makeDefaultIcon(UserHandle user) {
- try (BaseIconFactory li = getIconFactory()) {
- return li.makeDefaultIcon(user);
- }
- }
-
- /**
- * Remove any records for the supplied ComponentName.
- */
- public synchronized void remove(ComponentName componentName, UserHandle user) {
- mCache.remove(new ComponentKey(componentName, user));
- }
-
- /**
- * Remove any records for the supplied package name from memory.
- */
- private void removeFromMemCacheLocked(String packageName, UserHandle user) {
- HashSet forDeletion = new HashSet<>();
- for (ComponentKey key: mCache.keySet()) {
- if (key.componentName.getPackageName().equals(packageName)
- && key.user.equals(user)) {
- forDeletion.add(key);
- }
- }
- for (ComponentKey condemned: forDeletion) {
- mCache.remove(condemned);
- }
- }
-
- /**
- * Removes the entries related to the given package in memory and persistent DB.
- */
- public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
- removeFromMemCacheLocked(packageName, user);
- long userSerial = getSerialNumberForUser(user);
- mIconDb.delete(
- IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[]{packageName + "/%", Long.toString(userSerial)});
- }
-
- public IconCacheUpdateHandler getUpdateHandler() {
- updateSystemState();
- return new IconCacheUpdateHandler(this);
- }
-
- /**
- * Refreshes the system state definition used to check the validity of the cache. It
- * incorporates all the properties that can affect the cache like the list of enabled locale
- * and system-version.
- */
- private void updateSystemState() {
- mLocaleList = mContext.getResources().getConfiguration().getLocales();
- mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT;
- }
-
- protected String getIconSystemState(String packageName) {
- return mSystemState;
- }
-
- /**
- * Adds an entry into the DB and the in-memory cache.
- * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
- * the memory. This is useful then the previous bitmap was created using
- * old data.
- * package private
- */
- protected synchronized void addIconToDBAndMemCache(T object, CachingLogic cachingLogic,
- PackageInfo info, long userSerial, boolean replaceExisting) {
- UserHandle user = cachingLogic.getUser(object);
- ComponentName componentName = cachingLogic.getComponent(object);
-
- final ComponentKey key = new ComponentKey(componentName, user);
- CacheEntry entry = null;
- if (!replaceExisting) {
- entry = mCache.get(key);
- // We can't reuse the entry if the high-res icon is not present.
- if (entry == null || entry.icon == null || entry.isLowRes()) {
- entry = null;
- }
- }
- if (entry == null) {
- entry = new CacheEntry();
- cachingLogic.loadIcon(mContext, object, entry);
- }
- // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded
- // (e.g. fallback icon, default icon). So we drop here since there's no point in caching
- // an empty entry.
- if (entry.icon == null) return;
- entry.title = cachingLogic.getLabel(object);
- entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
- if (cachingLogic.addToMemCache()) mCache.put(key, entry);
-
- ContentValues values = newContentValues(entry, entry.title.toString(),
- componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
- addIconToDB(values, componentName, info, userSerial);
- }
-
- /**
- * Updates {@param values} to contain versioning information and adds it to the DB.
- * @param values {@link ContentValues} containing icon & title
- */
- private void addIconToDB(ContentValues values, ComponentName key,
- PackageInfo info, long userSerial) {
- values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
- values.put(IconDB.COLUMN_USER, userSerial);
- values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
- values.put(IconDB.COLUMN_VERSION, info.versionCode);
- mIconDb.insertOrReplace(values);
- }
-
- public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
- if (!mDefaultIcons.containsKey(user)) {
- mDefaultIcons.put(user, makeDefaultIcon(user));
- }
- return mDefaultIcons.get(user);
- }
-
- public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
- return getDefaultIcon(user).icon == icon;
- }
-
- /**
- * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
- * This method is not thread safe, it must be called from a synchronized method.
- */
- protected CacheEntry cacheLocked(
- @NonNull ComponentName componentName, @NonNull UserHandle user,
- @NonNull Supplier infoProvider, @NonNull CachingLogic cachingLogic,
- boolean usePackageIcon, boolean useLowResIcon) {
- assertWorkerThread();
- ComponentKey cacheKey = new ComponentKey(componentName, user);
- CacheEntry entry = mCache.get(cacheKey);
- if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
- entry = new CacheEntry();
- if (cachingLogic.addToMemCache()) {
- mCache.put(cacheKey, entry);
- }
-
- // Check the DB first.
- T object = null;
- boolean providerFetchedOnce = false;
-
- if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
- object = infoProvider.get();
- providerFetchedOnce = true;
-
- if (object != null) {
- cachingLogic.loadIcon(mContext, object, entry);
- } else {
- if (usePackageIcon) {
- CacheEntry packageEntry = getEntryForPackageLocked(
- componentName.getPackageName(), user, false);
- if (packageEntry != null) {
- if (DEBUG) Log.d(TAG, "using package default icon for " +
- componentName.toShortString());
- packageEntry.applyTo(entry);
- entry.title = packageEntry.title;
- entry.contentDescription = packageEntry.contentDescription;
- }
- }
- if (entry.icon == null) {
- if (DEBUG) Log.d(TAG, "using default icon for " +
- componentName.toShortString());
- getDefaultIcon(user).applyTo(entry);
- }
- }
- }
-
- if (TextUtils.isEmpty(entry.title)) {
- if (object == null && !providerFetchedOnce) {
- object = infoProvider.get();
- providerFetchedOnce = true;
- }
- if (object != null) {
- entry.title = cachingLogic.getLabel(object);
- entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
- }
- }
- }
- return entry;
- }
-
- public synchronized void clear() {
- assertWorkerThread();
- mIconDb.clear();
- }
-
- /**
- * Adds a default package entry in the cache. This entry is not persisted and will be removed
- * when the cache is flushed.
- */
- public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
- Bitmap icon, CharSequence title) {
- removeFromMemCacheLocked(packageName, user);
-
- ComponentKey cacheKey = getPackageKey(packageName, user);
- CacheEntry entry = mCache.get(cacheKey);
-
- // For icon caching, do not go through DB. Just update the in-memory entry.
- if (entry == null) {
- entry = new CacheEntry();
- }
- if (!TextUtils.isEmpty(title)) {
- entry.title = title;
- }
- if (icon != null) {
- BaseIconFactory li = getIconFactory();
- li.createIconBitmap(icon).applyTo(entry);
- li.close();
- }
- if (!TextUtils.isEmpty(title) && entry.icon != null) {
- mCache.put(cacheKey, entry);
- }
- }
-
- private static ComponentKey getPackageKey(String packageName, UserHandle user) {
- ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
- return new ComponentKey(cn, user);
- }
-
- /**
- * Gets an entry for the package, which can be used as a fallback entry for various components.
- * This method is not thread safe, it must be called from a synchronized method.
- */
- protected CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
- boolean useLowResIcon) {
- assertWorkerThread();
- ComponentKey cacheKey = getPackageKey(packageName, user);
- CacheEntry entry = mCache.get(cacheKey);
-
- if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
- entry = new CacheEntry();
- boolean entryUpdated = true;
-
- // Check the DB first.
- if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
- try {
- int flags = Process.myUserHandle().equals(user) ? 0 :
- PackageManager.GET_UNINSTALLED_PACKAGES;
- PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
- ApplicationInfo appInfo = info.applicationInfo;
- if (appInfo == null) {
- throw new NameNotFoundException("ApplicationInfo is null");
- }
-
- BaseIconFactory li = getIconFactory();
- // Load the full res icon for the application, but if useLowResIcon is set, then
- // only keep the low resolution icon instead of the larger full-sized icon
- BitmapInfo iconInfo = li.createBadgedIconBitmap(
- appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
- isInstantApp(appInfo));
- li.close();
-
- entry.title = appInfo.loadLabel(mPackageManager);
- entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
- entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon;
- entry.color = iconInfo.color;
-
- // Add the icon in the DB here, since these do not get written during
- // package updates.
- ContentValues values = newContentValues(
- iconInfo, entry.title.toString(), packageName, null);
- addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user));
-
- } catch (NameNotFoundException e) {
- if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
- entryUpdated = false;
- }
- }
-
- // Only add a filled-out entry to the cache
- if (entryUpdated) {
- mCache.put(cacheKey, entry);
- }
- }
- return entry;
- }
-
- private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
- Cursor c = null;
- try {
- c = mIconDb.query(
- lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
- IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
- new String[]{
- cacheKey.componentName.flattenToString(),
- Long.toString(getSerialNumberForUser(cacheKey.user))});
- if (c.moveToNext()) {
- // Set the alpha to be 255, so that we never have a wrong color
- entry.color = setColorAlphaBound(c.getInt(0), 255);
- entry.title = c.getString(1);
- if (entry.title == null) {
- entry.title = "";
- entry.contentDescription = "";
- } else {
- entry.contentDescription = mPackageManager.getUserBadgedLabel(
- entry.title, cacheKey.user);
- }
-
- if (lowRes) {
- entry.icon = LOW_RES_ICON;
- } else {
- byte[] data = c.getBlob(2);
- try {
- entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length,
- mDecodeOptions);
- } catch (Exception e) { }
- }
- return true;
- }
- } catch (SQLiteException e) {
- Log.d(TAG, "Error reading icon cache", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return false;
- }
-
- /**
- * Returns a cursor for an arbitrary query to the cache db
- */
- public synchronized Cursor queryCacheDb(String[] columns, String selection,
- String[] selectionArgs) {
- return mIconDb.query(columns, selection, selectionArgs);
- }
-
- /**
- * Cache class to store the actual entries on disk
- */
- public static final class IconDB extends SQLiteCacheHelper {
- private static final int RELEASE_VERSION = 27;
-
- public static final String TABLE_NAME = "icons";
- public static final String COLUMN_ROWID = "rowid";
- public static final String COLUMN_COMPONENT = "componentName";
- public static final String COLUMN_USER = "profileId";
- public static final String COLUMN_LAST_UPDATED = "lastUpdated";
- public static final String COLUMN_VERSION = "version";
- public static final String COLUMN_ICON = "icon";
- public static final String COLUMN_ICON_COLOR = "icon_color";
- public static final String COLUMN_LABEL = "label";
- public static final String COLUMN_SYSTEM_STATE = "system_state";
- public static final String COLUMN_KEYWORDS = "keywords";
-
- public static final String[] COLUMNS_HIGH_RES = new String[] {
- IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
- public static final String[] COLUMNS_LOW_RES = new String[] {
- IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
-
- public IconDB(Context context, String dbFileName, int iconPixelSize) {
- super(context, dbFileName, (RELEASE_VERSION << 16) + iconPixelSize, TABLE_NAME);
- }
-
- @Override
- protected void onCreateTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
- + COLUMN_COMPONENT + " TEXT NOT NULL, "
- + COLUMN_USER + " INTEGER NOT NULL, "
- + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_ICON + " BLOB, "
- + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, "
- + COLUMN_LABEL + " TEXT, "
- + COLUMN_SYSTEM_STATE + " TEXT, "
- + COLUMN_KEYWORDS + " TEXT, "
- + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") "
- + ");");
- }
- }
-
- private ContentValues newContentValues(BitmapInfo bitmapInfo, String label,
- String packageName, @Nullable String keywords) {
- ContentValues values = new ContentValues();
- values.put(IconDB.COLUMN_ICON,
- bitmapInfo.isLowRes() ? null : GraphicsUtils.flattenBitmap(bitmapInfo.icon));
- values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color);
-
- values.put(IconDB.COLUMN_LABEL, label);
- values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName));
- values.put(IconDB.COLUMN_KEYWORDS, keywords);
- return values;
- }
-
- private void assertWorkerThread() {
- if (Looper.myLooper() != mBgLooper) {
- throw new IllegalStateException("Cache accessed on wrong thread " + Looper.myLooper());
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
deleted file mode 100644
index e40a9c2c96..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons.cache;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.LocaleList;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.icons.BitmapInfo;
-
-public interface CachingLogic {
-
- ComponentName getComponent(T object);
-
- UserHandle getUser(T object);
-
- CharSequence getLabel(T object);
-
- void loadIcon(Context context, T object, BitmapInfo target);
-
- /**
- * Provides a option list of keywords to associate with this object
- */
- @Nullable
- default String getKeywords(T object, LocaleList localeList) {
- return null;
- }
-
- /**
- * Returns true the object should be added to mem cache; otherwise returns false.
- */
- default boolean addToMemCache() {
- return true;
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java b/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java
deleted file mode 100644
index ee52934543..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/HandlerRunnable.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons.cache;
-
-import android.os.Handler;
-
-/**
- * A runnable that can be posted to a {@link Handler} which can be canceled.
- */
-public abstract class HandlerRunnable implements Runnable {
-
- private final Handler mHandler;
- private final Runnable mEndRunnable;
-
- private boolean mEnded = false;
- private boolean mCanceled = false;
-
- public HandlerRunnable(Handler handler, Runnable endRunnable) {
- mHandler = handler;
- mEndRunnable = endRunnable;
- }
-
- /**
- * Cancels this runnable from being run, only if it has not already run.
- */
- public void cancel() {
- mHandler.removeCallbacks(this);
- // TODO: This can actually cause onEnd to be called twice if the handler is already running
- // this runnable
- // NOTE: This is currently run on whichever thread the caller is run on.
- mCanceled = true;
- onEnd();
- }
-
- /**
- * @return whether this runnable was canceled.
- */
- protected boolean isCanceled() {
- return mCanceled;
- }
-
- /**
- * To be called by the implemention of this runnable. The end callback is done on whichever
- * thread the caller is calling from.
- */
- public void onEnd() {
- if (!mEnded) {
- mEnded = true;
- if (mEndRunnable != null) {
- mEndRunnable.run();
- }
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
deleted file mode 100644
index 8224966d87..0000000000
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons.cache;
-
-import android.content.ComponentName;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-
-import com.android.launcher3.icons.cache.BaseIconCache.IconDB;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.Stack;
-
-/**
- * Utility class to handle updating the Icon cache
- */
-public class IconCacheUpdateHandler {
-
- private static final String TAG = "IconCacheUpdateHandler";
-
- /**
- * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}.
- * This mode is used for the first run.
- */
- private static final boolean MODE_SET_INVALID_ITEMS = true;
-
- /**
- * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all
- * subsequent runs, which essentially acts as set-union of all valid items.
- */
- private static final boolean MODE_CLEAR_VALID_ITEMS = false;
-
- private static final Object ICON_UPDATE_TOKEN = new Object();
-
- private final HashMap mPkgInfoMap;
- private final BaseIconCache mIconCache;
-
- private final HashMap> mPackagesToIgnore = new HashMap<>();
-
- private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray();
- private boolean mFilterMode = MODE_SET_INVALID_ITEMS;
-
- IconCacheUpdateHandler(BaseIconCache cache) {
- mIconCache = cache;
-
- mPkgInfoMap = new HashMap<>();
-
- // Remove all active icon update tasks.
- mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
-
- createPackageInfoMap();
- }
-
- public void setPackagesToIgnore(UserHandle userHandle, Set packages) {
- mPackagesToIgnore.put(userHandle, packages);
- }
-
- private void createPackageInfoMap() {
- PackageManager pm = mIconCache.mPackageManager;
- for (PackageInfo info :
- pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)) {
- mPkgInfoMap.put(info.packageName, info);
- }
- }
-
- /**
- * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
- * the DB and are updated.
- * @return The set of packages for which icons have updated.
- */
- public void updateIcons(List apps, CachingLogic cachingLogic,
- OnUpdateCallback onUpdateCallback) {
- // Filter the list per user
- HashMap> userComponentMap = new HashMap<>();
- int count = apps.size();
- for (int i = 0; i < count; i++) {
- T app = apps.get(i);
- UserHandle userHandle = cachingLogic.getUser(app);
- HashMap componentMap = userComponentMap.get(userHandle);
- if (componentMap == null) {
- componentMap = new HashMap<>();
- userComponentMap.put(userHandle, componentMap);
- }
- componentMap.put(cachingLogic.getComponent(app), app);
- }
-
- for (Entry> entry : userComponentMap.entrySet()) {
- updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback);
- }
-
- // From now on, clear every valid item from the global valid map.
- mFilterMode = MODE_CLEAR_VALID_ITEMS;
- }
-
- /**
- * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
- * the DB and are updated.
- * @return The set of packages for which icons have updated.
- */
- @SuppressWarnings("unchecked")
- private void updateIconsPerUser(UserHandle user, HashMap componentMap,
- CachingLogic cachingLogic, OnUpdateCallback onUpdateCallback) {
- Set ignorePackages = mPackagesToIgnore.get(user);
- if (ignorePackages == null) {
- ignorePackages = Collections.emptySet();
- }
- long userSerial = mIconCache.getSerialNumberForUser(user);
-
- Stack appsToUpdate = new Stack<>();
-
- try (Cursor c = mIconCache.mIconDb.query(
- new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
- IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
- IconDB.COLUMN_SYSTEM_STATE},
- IconDB.COLUMN_USER + " = ? ",
- new String[]{Long.toString(userSerial)})) {
-
- final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
- final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
- final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
- final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
- final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
-
- while (c.moveToNext()) {
- String cn = c.getString(indexComponent);
- ComponentName component = ComponentName.unflattenFromString(cn);
- PackageInfo info = mPkgInfoMap.get(component.getPackageName());
-
- int rowId = c.getInt(rowIndex);
- if (info == null) {
- if (!ignorePackages.contains(component.getPackageName())) {
-
- if (mFilterMode == MODE_SET_INVALID_ITEMS) {
- mIconCache.remove(component, user);
- mItemsToDelete.put(rowId, true);
- }
- }
- continue;
- }
- if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
- // Application is not present
- continue;
- }
-
- long updateTime = c.getLong(indexLastUpdate);
- int version = c.getInt(indexVersion);
- T app = componentMap.remove(component);
- if (version == info.versionCode && updateTime == info.lastUpdateTime
- && TextUtils.equals(c.getString(systemStateIndex),
- mIconCache.getIconSystemState(info.packageName))) {
-
- if (mFilterMode == MODE_CLEAR_VALID_ITEMS) {
- mItemsToDelete.put(rowId, false);
- }
- continue;
- }
- if (app == null) {
- if (mFilterMode == MODE_SET_INVALID_ITEMS) {
- mIconCache.remove(component, user);
- mItemsToDelete.put(rowId, true);
- }
- } else {
- appsToUpdate.add(app);
- }
- }
- } catch (SQLiteException e) {
- Log.d(TAG, "Error reading icon cache", e);
- // Continue updating whatever we have read so far
- }
-
- // Insert remaining apps.
- if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
- Stack appsToAdd = new Stack<>();
- appsToAdd.addAll(componentMap.values());
- new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic,
- onUpdateCallback).scheduleNext();
- }
- }
-
- /**
- * Commits all updates as part of the update handler to disk. Not more calls should be made
- * to this class after this.
- */
- public void finish() {
- // Commit all deletes
- int deleteCount = 0;
- StringBuilder queryBuilder = new StringBuilder()
- .append(IconDB.COLUMN_ROWID)
- .append(" IN (");
-
- int count = mItemsToDelete.size();
- for (int i = 0; i < count; i++) {
- if (mItemsToDelete.valueAt(i)) {
- if (deleteCount > 0) {
- queryBuilder.append(", ");
- }
- queryBuilder.append(mItemsToDelete.keyAt(i));
- deleteCount++;
- }
- }
- queryBuilder.append(')');
-
- if (deleteCount > 0) {
- mIconCache.mIconDb.delete(queryBuilder.toString(), null);
- }
- }
-
-
- /**
- * A runnable that updates invalid icons and adds missing icons in the DB for the provided
- * LauncherActivityInfo list. Items are updated/added one at a time, so that the
- * worker thread doesn't get blocked.
- */
- private class SerializedIconUpdateTask implements Runnable {
- private final long mUserSerial;
- private final UserHandle mUserHandle;
- private final Stack mAppsToAdd;
- private final Stack mAppsToUpdate;
- private final CachingLogic mCachingLogic;
- private final HashSet mUpdatedPackages = new HashSet<>();
- private final OnUpdateCallback mOnUpdateCallback;
-
- SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
- Stack appsToAdd, Stack appsToUpdate, CachingLogic cachingLogic,
- OnUpdateCallback onUpdateCallback) {
- mUserHandle = userHandle;
- mUserSerial = userSerial;
- mAppsToAdd = appsToAdd;
- mAppsToUpdate = appsToUpdate;
- mCachingLogic = cachingLogic;
- mOnUpdateCallback = onUpdateCallback;
- }
-
- @Override
- public void run() {
- if (!mAppsToUpdate.isEmpty()) {
- T app = mAppsToUpdate.pop();
- String pkg = mCachingLogic.getComponent(app).getPackageName();
- PackageInfo info = mPkgInfoMap.get(pkg);
- mIconCache.addIconToDBAndMemCache(
- app, mCachingLogic, info, mUserSerial, true /*replace existing*/);
- mUpdatedPackages.add(pkg);
-
- if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
- // No more app to update. Notify callback.
- mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle);
- }
-
- // Let it run one more time.
- scheduleNext();
- } else if (!mAppsToAdd.isEmpty()) {
- T app = mAppsToAdd.pop();
- PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName());
- // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
- // app should have package info, this is not guaranteed by the api
- if (info != null) {
- mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info,
- mUserSerial, false /*replace existing*/);
- }
-
- if (!mAppsToAdd.isEmpty()) {
- scheduleNext();
- }
- }
- }
-
- public void scheduleNext() {
- mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
- SystemClock.uptimeMillis() + 1);
- }
- }
-
- public interface OnUpdateCallback {
-
- void onPackageIconsUpdated(HashSet updatedPackages, UserHandle user);
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java b/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java
deleted file mode 100644
index 34bed94270..0000000000
--- a/iconloaderlib/src/com/android/launcher3/util/ComponentKey.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.android.launcher3.util;
-
-/**
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-import java.util.Arrays;
-
-public class ComponentKey {
-
- public final ComponentName componentName;
- public final UserHandle user;
-
- private final int mHashCode;
-
- public ComponentKey(ComponentName componentName, UserHandle user) {
- if (componentName == null || user == null) {
- throw new NullPointerException();
- }
- this.componentName = componentName;
- this.user = user;
- mHashCode = Arrays.hashCode(new Object[] {componentName, user});
-
- }
-
- @Override
- public int hashCode() {
- return mHashCode;
- }
-
- @Override
- public boolean equals(Object o) {
- ComponentKey other = (ComponentKey) o;
- return other.componentName.equals(componentName) && other.user.equals(user);
- }
-
- /**
- * Encodes a component key as a string of the form [flattenedComponentString#userId].
- */
- @Override
- public String toString() {
- return componentName.flattenToString() + "#" + user;
- }
-}
\ No newline at end of file
diff --git a/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java b/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java
deleted file mode 100644
index fe864a2847..0000000000
--- a/iconloaderlib/src/com/android/launcher3/util/NoLocaleSQLiteHelper.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2018 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.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.database.DatabaseErrorHandler;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.database.sqlite.SQLiteDatabase.OpenParams;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Build;
-
-/**
- * Extension of {@link SQLiteOpenHelper} which avoids creating default locale table by
- * A context wrapper which creates databases without support for localized collators.
- */
-public abstract class NoLocaleSQLiteHelper extends SQLiteOpenHelper {
-
- private static final boolean ATLEAST_P =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
-
- public NoLocaleSQLiteHelper(Context context, String name, int version) {
- super(ATLEAST_P ? context : new NoLocalContext(context), name, null, version);
- if (ATLEAST_P) {
- setOpenParams(new OpenParams.Builder().addOpenFlags(NO_LOCALIZED_COLLATORS).build());
- }
- }
-
- private static class NoLocalContext extends ContextWrapper {
- public NoLocalContext(Context base) {
- super(base);
- }
-
- @Override
- public SQLiteDatabase openOrCreateDatabase(
- String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
- return super.openOrCreateDatabase(
- name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
- }
- }
-}
diff --git a/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java b/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java
deleted file mode 100644
index 49de4bd1bf..0000000000
--- a/iconloaderlib/src/com/android/launcher3/util/SQLiteCacheHelper.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package com.android.launcher3.util;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteFullException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-/**
- * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
- * Any exception during write operations are ignored, and any version change causes a DB reset.
- */
-public abstract class SQLiteCacheHelper {
- private static final String TAG = "SQLiteCacheHelper";
-
- private static final boolean IN_MEMORY_CACHE = false;
-
- private final String mTableName;
- private final MySQLiteOpenHelper mOpenHelper;
-
- private boolean mIgnoreWrites;
-
- public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
- if (IN_MEMORY_CACHE) {
- name = null;
- }
- mTableName = tableName;
- mOpenHelper = new MySQLiteOpenHelper(context, name, version);
-
- mIgnoreWrites = false;
- }
-
- /**
- * @see SQLiteDatabase#delete(String, String, String[])
- */
- public void delete(String whereClause, String[] whereArgs) {
- if (mIgnoreWrites) {
- return;
- }
- try {
- mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs);
- } catch (SQLiteFullException e) {
- onDiskFull(e);
- } catch (SQLiteException e) {
- Log.d(TAG, "Ignoring sqlite exception", e);
- }
- }
-
- /**
- * @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int)
- */
- public void insertOrReplace(ContentValues values) {
- if (mIgnoreWrites) {
- return;
- }
- try {
- mOpenHelper.getWritableDatabase().insertWithOnConflict(
- mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE);
- } catch (SQLiteFullException e) {
- onDiskFull(e);
- } catch (SQLiteException e) {
- Log.d(TAG, "Ignoring sqlite exception", e);
- }
- }
-
- private void onDiskFull(SQLiteFullException e) {
- Log.e(TAG, "Disk full, all write operations will be ignored", e);
- mIgnoreWrites = true;
- }
-
- /**
- * @see SQLiteDatabase#query(String, String[], String, String[], String, String, String)
- */
- public Cursor query(String[] columns, String selection, String[] selectionArgs) {
- return mOpenHelper.getReadableDatabase().query(
- mTableName, columns, selection, selectionArgs, null, null, null);
- }
-
- public void clear() {
- mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
- }
-
- public void close() {
- mOpenHelper.close();
- }
-
- protected abstract void onCreateTable(SQLiteDatabase db);
-
- /**
- * A private inner class to prevent direct DB access.
- */
- private class MySQLiteOpenHelper extends NoLocaleSQLiteHelper {
-
- public MySQLiteOpenHelper(Context context, String name, int version) {
- super(context, name, version);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- onCreateTable(db);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion != newVersion) {
- clearDB(db);
- }
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion != newVersion) {
- clearDB(db);
- }
- }
-
- private void clearDB(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + mTableName);
- onCreate(db);
- }
- }
-}
diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java
deleted file mode 100644
index 48f11fde3b..0000000000
--- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/IconFactory.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons;
-
-import android.content.Context;
-
-/**
- * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
- * that are threadsafe.
- */
-public class IconFactory extends BaseIconFactory {
-
- private static final Object sPoolSync = new Object();
- private static IconFactory sPool;
- private static int sPoolId = 0;
-
- /**
- * Return a new Message instance from the global pool. Allows us to
- * avoid allocating new objects in many cases.
- */
- public static IconFactory obtain(Context context) {
- int poolId;
- synchronized (sPoolSync) {
- if (sPool != null) {
- IconFactory m = sPool;
- sPool = m.next;
- m.next = null;
- return m;
- }
- poolId = sPoolId;
- }
-
- return new IconFactory(context,
- context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
- poolId);
- }
-
- public static void clearPool() {
- synchronized (sPoolSync) {
- sPool = null;
- sPoolId++;
- }
- }
-
- private final int mPoolId;
-
- private IconFactory next;
-
- private IconFactory(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
- super(context, fillResIconDpi, iconBitmapSize);
- mPoolId = poolId;
- }
-
- /**
- * Recycles a LauncherIcons that may be in-use.
- */
- public void recycle() {
- synchronized (sPoolSync) {
- if (sPoolId != mPoolId) {
- return;
- }
- // Clear any temporary state variables
- clear();
-
- next = sPool;
- sPool = this;
- }
- }
-
- @Override
- public void close() {
- recycle();
- }
-}
diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
deleted file mode 100644
index 1337975f19..0000000000
--- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons;
-
-import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED;
-import static android.content.Intent.ACTION_MANAGED_PROFILE_REMOVED;
-
-import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.SparseLongArray;
-
-import com.android.launcher3.icons.cache.BaseIconCache;
-
-/**
- * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
- * that are threadsafe.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class SimpleIconCache extends BaseIconCache {
-
- private static SimpleIconCache sIconCache = null;
- private static final Object CACHE_LOCK = new Object();
-
- private final SparseLongArray mUserSerialMap = new SparseLongArray(2);
- private final UserManager mUserManager;
-
- public SimpleIconCache(Context context, String dbFileName, Looper bgLooper, int iconDpi,
- int iconPixelSize, boolean inMemoryCache) {
- super(context, dbFileName, bgLooper, iconDpi, iconPixelSize, inMemoryCache);
- mUserManager = context.getSystemService(UserManager.class);
-
- // Listen for user cache changes.
- IntentFilter filter = new IntentFilter(ACTION_MANAGED_PROFILE_ADDED);
- filter.addAction(ACTION_MANAGED_PROFILE_REMOVED);
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- resetUserCache();
- }
- }, filter, null, new Handler(bgLooper), 0);
- }
-
- @Override
- protected long getSerialNumberForUser(UserHandle user) {
- synchronized (mUserSerialMap) {
- int index = mUserSerialMap.indexOfKey(user.getIdentifier());
- if (index >= 0) {
- return mUserSerialMap.valueAt(index);
- }
- long serial = mUserManager.getSerialNumberForUser(user);
- mUserSerialMap.put(user.getIdentifier(), serial);
- return serial;
- }
- }
-
- private void resetUserCache() {
- synchronized (mUserSerialMap) {
- mUserSerialMap.clear();
- }
- }
-
- @Override
- protected boolean isInstantApp(ApplicationInfo info) {
- return info.isInstantApp();
- }
-
- @Override
- protected BaseIconFactory getIconFactory() {
- return IconFactory.obtain(mContext);
- }
-
- public static SimpleIconCache getIconCache(Context context) {
- synchronized (CACHE_LOCK) {
- if (sIconCache != null) {
- return sIconCache;
- }
- boolean inMemoryCache =
- context.getResources().getBoolean(R.bool.simple_cache_enable_im_memory);
- String dbFileName = context.getString(R.string.cache_db_name);
-
- HandlerThread bgThread = new HandlerThread("simple-icon-cache");
- bgThread.start();
-
- sIconCache = new SimpleIconCache(context.getApplicationContext(), dbFileName,
- bgThread.getLooper(), context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.default_icon_bitmap_size),
- inMemoryCache);
- return sIconCache;
- }
- }
-}
diff --git a/proguard.flags b/proguard.flags
index 272ab7a7f1..37b80938fa 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -2,12 +2,6 @@
*;
}
-# Proguard will strip new callbacks in LauncherApps.Callback from
-# WrappedCallback if compiled against an older SDK. Don't let this happen.
--keep class com.android.launcher3.compat.** {
- *;
-}
-
-keep class com.android.launcher3.graphics.ShadowDrawable {
public (...);
}
@@ -23,7 +17,10 @@
# support jar.
-keep class androidx.recyclerview.widget.RecyclerView { *; }
-# Preference fragments
+# Fragments
+-keep class ** extends androidx.fragment.app.Fragment {
+ public (...);
+}
-keep class ** extends android.app.Fragment {
public (...);
}
@@ -50,4 +47,16 @@
-dontwarn android.app.**
-dontwarn android.view.**
-dontwarn android.os.**
--dontwarn android.graphics.**
\ No newline at end of file
+-dontwarn android.graphics.**
+
+# Ignore warnings for hidden utility classes referenced from the shared lib
+-dontwarn com.android.internal.util.**
+
+################ Do not optimize recents lib #############
+-keep class com.android.systemui.** {
+ *;
+}
+
+-keep class com.android.quickstep.** {
+ *;
+}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
new file mode 100644
index 0000000000..561196941c
--- /dev/null
+++ b/protos/launcher_atom.proto
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.logger";
+option java_outer_classname = "LauncherAtom";
+
+//
+// ItemInfos
+message ItemInfo {
+ oneof Item {
+ Application application = 1;
+ Task task = 2;
+ Shortcut shortcut = 3;
+ Widget widget = 4;
+ FolderIcon folder_icon = 9;
+ }
+ // When used for launch event, stores the global predictive rank
+ optional int32 rank = 5;
+
+ // Stores whether the Item belows to non primary user
+ optional bool is_work = 6;
+
+ // Item can be child node to parent container or parent containers (nested)
+ optional ContainerInfo container_info = 7;
+
+ // Stores the origin of the Item
+ optional Attribute attribute = 8;
+}
+
+// Represents various launcher surface where items are placed.
+message ContainerInfo {
+ oneof Container {
+ WorkspaceContainer workspace = 1;
+ HotseatContainer hotseat = 2;
+ FolderContainer folder = 3;
+ AllAppsContainer all_apps_container = 4;
+ WidgetsContainer widgets_container = 5;
+ PredictionContainer prediction_container = 6;
+ SearchResultContainer search_result_container = 7;
+ ShortcutsContainer shortcuts_container = 8;
+ SettingsContainer settings_container = 9;
+ PredictedHotseatContainer predicted_hotseat_container = 10;
+ TaskSwitcherContainer task_switcher_container = 11;
+ }
+}
+
+// Represents the apps list sorted alphabetically inside the all-apps view.
+message AllAppsContainer {
+}
+
+message WidgetsContainer {
+}
+
+// Represents the predicted apps row(top row) in the all-apps view.
+message PredictionContainer {
+}
+
+// Represents the apps container within search results.
+message SearchResultContainer {
+
+ // Length of search term.
+ optional int32 query_length = 1;
+
+ // Container from where search was invoked.
+ oneof ParentContainer {
+ WorkspaceContainer workspace = 2;
+ AllAppsContainer all_apps_container = 3;
+ }
+}
+
+// Container for package specific shortcuts to deep links and notifications.
+// Typically shown as popup window by longpressing on an icon.
+message ShortcutsContainer {
+}
+
+// Container for generic system shortcuts for launcher specific settings.
+// Typically shown up as popup window by longpressing on empty space on workspace.
+message SettingsContainer {
+}
+
+message TaskSwitcherContainer {
+}
+
+enum Attribute {
+ UNKNOWN = 0;
+ DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat
+ BACKUP_RESTORE = 2; // icon layout restored from backup
+ PINITEM = 3; // from another app (e.g., Chrome's "Add to Home screen")
+ ALLAPPS_ATOZ = 4; // within launcher surface, all aps a-z
+ WIDGETS = 5; // within launcher, widgets tray
+ ADD_TO_HOMESCREEN = 6; // play install + launcher home setting
+ ALLAPPS_PREDICTION = 7; // from prediction bar in all apps container
+ HOTSEAT_PREDICTION = 8; // from prediction bar in hotseat container
+
+ // Folder's label is one of the non-empty suggested values.
+ SUGGESTED_LABEL = 9;
+
+ // Folder's label is non-empty, manually entered by the user
+ // and different from any of suggested values.
+ MANUAL_LABEL = 10;
+
+ // Folder's label is not yet assigned( i.e., title == null).
+ // Eligible for auto-labeling.
+ UNLABELED = 11;
+
+ // Folder's label is empty(i.e., title == "").
+ // Not eligible for auto-labeling.
+ EMPTY_LABEL = 12;
+}
+
+// Main app icons
+message Application {
+ optional string package_name = 1;
+ optional string component_name = 2;
+}
+
+// Legacy shortcuts and shortcuts handled by ShortcutManager
+message Shortcut {
+ optional string shortcut_name = 1;
+}
+
+// AppWidgets handled by AppWidgetManager
+message Widget {
+ optional int32 span_x = 1 [default = 1];
+ optional int32 span_y = 2 [default = 1];
+ optional int32 app_widget_id = 3;
+ optional string package_name = 4; // only populated during snapshot if from workspace
+ optional string component_name = 5; // only populated during snapshot if from workspace
+}
+
+// Tasks handled by PackageManager
+message Task {
+ optional string package_name = 1;
+ optional string component_name = 2;
+ optional int32 index = 3;
+}
+
+// Represents folder in a closed state.
+message FolderIcon {
+ // Number of items inside folder.
+ optional int32 cardinality = 1;
+
+ // State of the folder label before the event.
+ optional FromState from_label_state = 2;
+
+ // State of the folder label after the event.
+ optional ToState to_label_state = 3;
+
+ // Details about actual folder label.
+ // Populated when folder label is not a PII.
+ optional string label_info = 4;
+}
+
+//////////////////////////////////////////////
+// Containers
+
+message WorkspaceContainer {
+ optional int32 page_index = 1 [default = -2]; // range [-1, l], 0 is the index of the main homescreen
+ optional int32 grid_x = 2 [default = -1]; // [0, m], m varies based on the display density and resolution
+ optional int32 grid_y = 3 [default = -1]; // [0, n], n varies based on the display density and resolution
+}
+
+message HotseatContainer {
+ optional int32 index = 1;
+}
+
+// Represents hotseat container with prediction feature enabled.
+message PredictedHotseatContainer {
+ optional int32 index = 1;
+
+ // No of hotseat positions filled with predicted items.
+ optional int32 cardinality = 2;
+}
+
+message FolderContainer {
+ optional int32 page_index = 1 [default = -1];
+ optional int32 grid_x = 2 [default = -1];
+ optional int32 grid_y = 3 [default = -1];
+ oneof ParentContainer {
+ WorkspaceContainer workspace = 4;
+ HotseatContainer hotseat = 5;
+ }
+}
+
+// Represents state of EditText field before update.
+enum FromState {
+ // Default value.
+ // Used when a FromState is not applicable, for example, during folder creation.
+ FROM_STATE_UNSPECIFIED = 0;
+
+ // EditText was empty.
+ // Eg: When a folder label is updated from empty string.
+ FROM_EMPTY = 1;
+
+ // EditText was non-empty and manually entered by the user.
+ // Eg: When a folder label is updated from a user-entered value.
+ FROM_CUSTOM = 2;
+
+ // EditText was non-empty and one of the suggestions.
+ // Eg: When a folder label is updated from a suggested value.
+ FROM_SUGGESTED = 3;
+}
+
+// Represents state of EditText field after update.
+enum ToState {
+ // Default value.
+ // Used when ToState is not applicable, for example, when folder label is updated to a different
+ // value when folder label suggestion feature is disabled.
+ TO_STATE_UNSPECIFIED = 0;
+
+ // User attempted to change the EditText, but was not changed.
+ UNCHANGED = 1;
+
+ // New label matches with primary(aka top) suggestion.
+ TO_SUGGESTION0 = 2;
+
+ // New value matches with second top suggestion even though the top suggestion was non-empty.
+ TO_SUGGESTION1_WITH_VALID_PRIMARY = 3;
+
+ // New value matches with second top suggestion given that top suggestion was empty.
+ TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 4;
+
+ // New value matches with third top suggestion even though the top suggestion was non-empty.
+ TO_SUGGESTION2_WITH_VALID_PRIMARY = 5;
+
+ // New value matches with third top suggestion given that top suggestion was empty.
+ TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 6;
+
+ // New value matches with 4th top suggestion even though the top suggestion was non-empty.
+ TO_SUGGESTION3_WITH_VALID_PRIMARY = 7;
+
+ // New value matches with 4th top suggestion given that top suggestion was empty.
+ TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 8;
+
+ // New value is empty even though the top suggestion was non-empty.
+ TO_EMPTY_WITH_VALID_PRIMARY = 9;
+
+ // New value is empty given that top suggestion was empty.
+ TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 10;
+
+ // New value is empty given that no suggestions were provided.
+ TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 11;
+
+ // New value is empty given that suggestions feature was disabled.
+ TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 12;
+
+ // New value is non-empty and does not match with any of the suggestions even though the top suggestion was non-empty.
+ TO_CUSTOM_WITH_VALID_PRIMARY = 13;
+
+ // New value is non-empty and not match with any suggestions given that top suggestion was empty.
+ TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 14;
+
+ // New value is non-empty and also no suggestions were provided.
+ TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 15;
+
+ // New value is non-empty and also suggestions feature was disable.
+ TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 16;
+}
diff --git a/protos/launcher_dump.proto b/protos/launcher_dump.proto
deleted file mode 100644
index dc8fbda250..0000000000
--- a/protos/launcher_dump.proto
+++ /dev/null
@@ -1,75 +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.
- */
-syntax = "proto2";
-
-option java_package = "com.android.launcher3.model";
-option java_outer_classname = "LauncherDumpProto";
-
-package model;
-
-message DumpTarget {
- enum Type {
- NONE = 0;
- ITEM = 1;
- CONTAINER = 2;
- }
-
- optional Type type = 1;
- optional int32 page_id = 2;
- optional int32 grid_x = 3;
- optional int32 grid_y = 4;
-
- // For container types only
- optional ContainerType container_type = 5;
-
- // For item types only
- optional ItemType item_type = 6;
-
- optional string package_name = 7; // All ItemTypes except UNKNOWN type
- optional string component = 8; // All ItemTypes except UNKNOWN type
- optional string item_id = 9; // For Pinned Shortcuts and appWidgetId
-
- optional int32 span_x = 10 [default = 1];// Used for ItemType.WIDGET
- optional int32 span_y = 11 [default = 1];// Used for ItemType.WIDGET
- optional UserType user_type = 12;
-}
-
-// Used to define what type of item a Target would represent.
-enum ItemType {
- UNKNOWN_ITEMTYPE = 0; // Launcher specific items
- APP_ICON = 1; // Regular app icons
- WIDGET = 2; // Elements from AppWidgetManager
- SHORTCUT = 3; // ShortcutManager
-}
-
-// Used to define what type of container a Target would represent.
-enum ContainerType {
- UNKNOWN_CONTAINERTYPE = 0;
- WORKSPACE = 1;
- HOTSEAT = 2;
- FOLDER = 3;
-}
-
-// Used to define what type of control a Target would represent.
-enum UserType {
- DEFAULT = 0;
- WORK = 1;
-}
-
-// Main message;
-message LauncherImpression {
- repeated DumpTarget targets = 1;
-}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index fd36d4ba53..9423cb2642 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -57,6 +57,40 @@ message Target {
optional TargetExtension extension = 16;
optional TipType tip_type = 17;
optional int32 search_query_length = 18;
+ optional bool is_work_app = 19;
+ optional FromFolderLabelState from_folder_label_state = 20;
+ optional ToFolderLabelState to_folder_label_state = 21;
+
+ // Note: proto does not support duplicate enum values, even if they belong to different enum type.
+ // Hence "FROM" and "TO" prefix added.
+ enum FromFolderLabelState {
+ FROM_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+ FROM_EMPTY = 1;
+ FROM_CUSTOM = 2;
+ FROM_SUGGESTED = 3;
+ }
+
+ enum ToFolderLabelState {
+ TO_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+ TO_SUGGESTION0_WITH_VALID_PRIMARY = 1;
+ TO_SUGGESTION1_WITH_VALID_PRIMARY = 2;
+ TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 3;
+ TO_SUGGESTION2_WITH_VALID_PRIMARY = 4;
+ TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 5;
+ TO_SUGGESTION3_WITH_VALID_PRIMARY = 6;
+ TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 7;
+ TO_EMPTY_WITH_VALID_SUGGESTIONS = 8 [deprecated = true];
+ TO_EMPTY_WITH_VALID_PRIMARY = 15;
+ TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 16;
+ TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 9;
+ TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 10;
+ TO_CUSTOM_WITH_VALID_SUGGESTIONS = 11 [deprecated = true];
+ TO_CUSTOM_WITH_VALID_PRIMARY = 17;
+ TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 18;
+ TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 12;
+ TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 13;
+ UNCHANGED = 14;
+ }
}
// Used to define what type of item a Target would represent.
@@ -92,7 +126,7 @@ enum ContainerType {
TASKSWITCHER = 12; // Recents UI Container (QuickStep)
APP = 13; // Foreground activity is another app (QuickStep)
TIP = 14; // Onboarding texts (QuickStep)
- SIDELOADED_LAUNCHER = 15;
+ OTHER_LAUNCHER_APP = 15;
}
// Used to define what type of control a Target would represent.
@@ -119,6 +153,13 @@ enum ControlType {
BACK_GESTURE = 19;
UNDO = 20;
DISMISS_PREDICTION = 21;
+ HYBRID_HOTSEAT_ACCEPTED = 22;
+ HYBRID_HOTSEAT_CANCELED = 23;
+ OVERVIEW_ACTIONS_SHARE_BUTTON = 24;
+ OVERVIEW_ACTIONS_SCREENSHOT_BUTTON = 25;
+ OVERVIEW_ACTIONS_SELECT_BUTTON = 26;
+ SELECT_MODE_CLOSE_BUTTON = 27;
+ SELECT_MODE_ITEM = 28;
}
enum TipType {
@@ -128,6 +169,7 @@ enum TipType {
QUICK_SCRUB_TEXT = 3;
PREDICTION_TEXT = 4;
DWB_TOAST = 5;
+ HYBRID_HOTSEAT = 6;
}
// Used to define the action component of the LauncherEvent.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java b/protos/launcher_trace.proto
similarity index 64%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
rename to protos/launcher_trace.proto
index 65f323c7d6..c6f3543c09 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
+++ b/protos/launcher_trace.proto
@@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep;
-import android.app.Activity;
-import android.os.Bundle;
+syntax = "proto2";
-/**
- * Empty activity to start a recents transition
- */
-public class LockScreenRecentsActivity extends Activity {
+package com.android.launcher3.tracing;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- finish();
- }
+option java_multiple_files = true;
+
+message LauncherTraceProto {
+
+ optional TouchInteractionServiceProto touch_interaction_service = 1;
+}
+
+message TouchInteractionServiceProto {
+
+ optional bool service_connected = 1;
}
diff --git a/protos/launcher_trace_file.proto b/protos/launcher_trace_file.proto
new file mode 100644
index 0000000000..6ce182a2ad
--- /dev/null
+++ b/protos/launcher_trace_file.proto
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+import "launcher_trace.proto";
+
+package com.android.launcher3.tracing;
+
+option java_multiple_files = true;
+
+/* represents a file full of launcher trace entries.
+ Encoded, it should start with 0x9 0x4C 0x4E 0x43 0x48 0x52 0x54 0x52 0x43 (.LNCHRTRC), such
+ that they can be easily identified. */
+message LauncherTraceFileProto {
+
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x48434E4C; /* LNCH (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x43525452; /* RTRC (little-endian ASCII) */
+ }
+
+ optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
+ repeated LauncherTraceEntryProto entry = 2;
+}
+
+/* one launcher trace entry. */
+message LauncherTraceEntryProto {
+ /* required: elapsed realtime in nanos since boot of when this entry was logged */
+ optional fixed64 elapsed_realtime_nanos = 1;
+
+ optional LauncherTraceProto launcher = 3;
+}
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
new file mode 100644
index 0000000000..60afddb0a8
--- /dev/null
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 826a27553c..e49f2ecdc0 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -22,8 +22,15 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3" >
+
+
+
+
-
+
+
+
+
+
@@ -85,11 +100,28 @@
android:clearTaskOnLaunch="true"
android:exported="false" />
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/go/quickstep/res/drawable/default_thumbnail.xml b/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
similarity index 73%
rename from go/quickstep/res/drawable/default_thumbnail.xml
rename to quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
index ab22dcf827..df7cd8e437 100644
--- a/go/quickstep/res/drawable/default_thumbnail.xml
+++ b/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
@@ -1,6 +1,5 @@
-
-
-
-
+ android:shape="oval">
+
+
+
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml b/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml
new file mode 100644
index 0000000000..5a2dfb7b4a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
new file mode 100644
index 0000000000..4fda2a9b9b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml b/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
new file mode 100644
index 0000000000..e7ef6e699a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
index ef272ed806..cd64a94bab 100644
--- a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
+++ b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
@@ -13,19 +13,30 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-
-
+ android:clipChildren="false">
+
+
+
+
+
+
+
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
index 7f1425b988..fe57e9b3d5 100644
--- a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
+++ b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml
@@ -1,5 +1,4 @@
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
diff --git a/iconloaderlib/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
similarity index 80%
rename from iconloaderlib/res/values/dimens.xml
rename to quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
index e8c0c44f72..70a765a562 100644
--- a/iconloaderlib/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
@@ -1,5 +1,5 @@
-
-
- 24dp
-
+
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
new file mode 100644
index 0000000000..1dab48267b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
index 7426e30396..f03f118f38 100644
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -1,6 +1,21 @@
+
#fff
+ #39000000
#61000000
#61FFFFFF
diff --git a/iconloaderlib/AndroidManifest.xml b/quickstep/recents_ui_overrides/res/values/config.xml
similarity index 70%
rename from iconloaderlib/AndroidManifest.xml
rename to quickstep/recents_ui_overrides/res/values/config.xml
index b30258da2a..120e03456f 100644
--- a/iconloaderlib/AndroidManifest.xml
+++ b/quickstep/recents_ui_overrides/res/values/config.xml
@@ -1,12 +1,11 @@
-
-
-
-
+
+ 150
+
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index 863a8ba528..9266b0652b 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -1,4 +1,18 @@
+
1dp
20dp
@@ -6,6 +20,7 @@
10dp
12dp
20dp
+ 16dp
2dp
16dp
26dp
@@ -17,15 +32,7 @@
16dp
8dp
14sp
- 8dp
-
- 2dp
80dp
-
-
- 18dp
- 10dp
- -60dp
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml
index ed3ba929a8..6aa9619cc9 100644
--- a/quickstep/recents_ui_overrides/res/values/override.xml
+++ b/quickstep/recents_ui_overrides/res/values/override.xml
@@ -26,5 +26,7 @@
com.android.quickstep.QuickstepProcessInitializer
com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension
+
+ com.android.launcher3.hybridhotseat.HotseatPredictionModel
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 114fd8e10b..38adf39444 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,41 +16,25 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
-import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.SpringAnimationBuilder;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -61,13 +45,6 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
*/
public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
- public static final int INDEX_SHELF_ANIM = 0;
- public static final int INDEX_RECENTS_FADE_ANIM = 1;
- public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
- public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
-
- public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
-
public LauncherAppTransitionManagerImpl(Context context) {
super(context);
}
@@ -81,22 +58,23 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
@Override
protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
- @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+ @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
RecentsView recentsView = mLauncher.getOverviewPanel();
boolean skipLauncherChanges = !launcherClosing;
- TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
-
- ClipAnimationHelper helper = new ClipAnimationHelper(mLauncher);
- anim.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
- .setDuration(RECENTS_LAUNCH_DURATION));
+ TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
+ PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
+ createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
+ mLauncher.getDepthController(), pa);
+ anim.play(pa.buildAnim());
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
- launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper);
+ launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
@@ -149,74 +127,8 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
return () -> {
overview.setFreezeViewVisibility(false);
+ overview.setTranslationY(0);
mLauncher.getStateManager().reapplyState();
};
}
-
- @Override
- public int getStateElementAnimationsCount() {
- return 4;
- }
-
- @Override
- public Animator createStateElementAnimation(int index, float... values) {
- switch (index) {
- case INDEX_SHELF_ANIM: {
- AllAppsTransitionController aatc = mLauncher.getAllAppsController();
- Animator springAnim = aatc.createSpringAnimation(values);
-
- if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
- // Translate hotseat with the shelf until reaching overview.
- float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
- ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
- float shiftRange = aatc.getShiftRange();
- if (values.length == 1) {
- values = new float[] {aatc.getProgress(), values[0]};
- }
- ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
- hotseatAnim.addUpdateListener(anim -> {
- float progress = (Float) anim.getAnimatedValue();
- if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
- float hotseatShift = (progress - overviewProgress) * shiftRange;
- mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
- }
- });
- hotseatAnim.setInterpolator(LINEAR);
- hotseatAnim.setDuration(springAnim.getDuration());
-
- AnimatorSet anim = new AnimatorSet();
- anim.play(hotseatAnim);
- anim.play(springAnim);
- return anim;
- }
-
- return springAnim;
- }
- case INDEX_RECENTS_FADE_ANIM:
- return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
- RecentsView.CONTENT_ALPHA, values);
- case INDEX_RECENTS_TRANSLATE_X_ANIM:
- return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), VIEW_TRANSLATE_X)
- .setDampingRatio(0.8f)
- .setStiffness(250)
- .setValues(values)
- .build(mLauncher);
- case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
- AnimatorSetBuilder builder = new AnimatorSetBuilder();
- builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
- if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
- builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
- }
- LauncherStateManager stateManager = mLauncher.getStateManager();
- return stateManager.createAtomicAnimation(
- stateManager.getCurrentStableState(), OVERVIEW, builder,
- ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
- }
-
- default:
- return super.createStateElementAnimation(index, values);
- }
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
deleted file mode 100644
index c5c4add6b2..0000000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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;
-
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-
-import java.util.function.BiPredicate;
-
-public class LauncherInitListenerEx extends LauncherInitListener {
-
- public LauncherInitListenerEx(BiPredicate onInitListener) {
- super(onInitListener);
- }
-
- @Override
- protected boolean init(Launcher launcher, boolean alreadyOnHome) {
- PredictionUiStateManager.INSTANCE.get(launcher).switchClient(Client.OVERVIEW);
- return super.init(launcher, alreadyOnHome);
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
index d3042cf82e..8477b103a2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -16,133 +16,30 @@
package com.android.launcher3.appprediction;
+import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
-import android.content.Context;
-import android.graphics.CornerPathEffect;
-import android.graphics.Paint;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
+import android.os.UserManager;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.views.ArrowTipView;
import com.android.systemui.shared.system.LauncherEventUtil;
-import androidx.core.content.ContextCompat;
-
/**
- * All apps tip view aligned just above prediction apps, shown to users that enter all apps for the
+ * ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the
* first time.
*/
-public class AllAppsTipView extends AbstractFloatingView {
+public class AllAppsTipView {
private static final String ALL_APPS_TIP_SEEN = "launcher.all_apps_tip_seen";
- private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
- private static final long SHOW_DELAY_MS = 200;
- private static final long SHOW_DURATION_MS = 300;
- private static final long HIDE_DURATION_MS = 100;
-
- private final Launcher mLauncher;
- private final Handler mHandler = new Handler();
-
- private AllAppsTipView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- private AllAppsTipView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setOrientation(LinearLayout.VERTICAL);
-
- mLauncher = Launcher.getLauncher(context);
-
- init(context);
- }
-
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- close(true);
- }
- return false;
- }
-
- @Override
- protected void handleClose(boolean animate) {
- if (mIsOpen) {
- if (animate) {
- animate().alpha(0f)
- .withLayer()
- .setStartDelay(0)
- .setDuration(HIDE_DURATION_MS)
- .setInterpolator(Interpolators.ACCEL)
- .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
- .start();
- } else {
- animate().cancel();
- mLauncher.getDragLayer().removeView(this);
- }
- mLauncher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
- mIsOpen = false;
- }
- }
-
- @Override
- public void logActionCommand(int command) {
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_ON_BOARD_POPUP) != 0;
- }
-
- private void init(Context context) {
- inflate(context, R.layout.arrow_toast, this);
-
- TextView textView = findViewById(R.id.text);
- textView.setText(R.string.all_apps_prediction_tip);
-
- View dismissButton = findViewById(R.id.dismiss);
- dismissButton.setOnClickListener(view -> {
- mLauncher.getUserEventDispatcher().logActionTip(
- LauncherEventUtil.DISMISS, ALL_APPS_PREDICTION_TIPS);
- handleClose(true);
- });
-
- View arrowView = findViewById(R.id.arrow);
- ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
- ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
- arrowLp.width, arrowLp.height, false));
- Paint arrowPaint = arrowDrawable.getPaint();
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
- arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
- // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
- arrowPaint.setPathEffect(new CornerPathEffect(
- context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
- arrowView.setBackground(arrowDrawable);
-
- mIsOpen = true;
-
- mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
- }
private static boolean showAllAppsTipIfNecessary(Launcher launcher) {
FloatingHeaderView floatingHeaderView = launcher.getAppsView().getFloatingHeaderView();
@@ -151,33 +48,20 @@ public class AllAppsTipView extends AbstractFloatingView {
TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE) != null
|| !launcher.isInState(ALL_APPS)
|| hasSeenAllAppsTip(launcher)
- || UserManagerCompat.getInstance(launcher).isDemoUser()
+ || launcher.getSystemService(UserManager.class).isDemoUser()
|| Utilities.IS_RUNNING_IN_TEST_HARNESS) {
return false;
}
- AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(),
- null);
- launcher.getDragLayer().addView(allAppsTipView);
+ int[] coords = new int[2];
+ floatingHeaderView.findFixedRowByType(PredictionRowView.class).getLocationOnScreen(coords);
+ ArrowTipView arrowTipView = new ArrowTipView(launcher).setOnClosedCallback(() -> {
+ launcher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
+ launcher.getUserEventDispatcher().logActionTip(LauncherEventUtil.DISMISS,
+ ALL_APPS_PREDICTION_TIPS);
+ });
+ arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]);
- DragLayer.LayoutParams params = (DragLayer.LayoutParams) allAppsTipView.getLayoutParams();
- params.gravity = Gravity.CENTER_HORIZONTAL;
-
- int top = floatingHeaderView.findFixedRowByType(PredictionRowView.class).getTop();
- allAppsTipView.setY(top - launcher.getResources().getDimensionPixelSize(
- R.dimen.all_apps_tip_bottom_margin));
-
- allAppsTipView.setAlpha(0);
- allAppsTipView.animate()
- .alpha(1f)
- .withLayer()
- .setStartDelay(SHOW_DELAY_MS)
- .setDuration(SHOW_DURATION_MS)
- .setInterpolator(Interpolators.DEACCEL)
- .start();
-
- launcher.getUserEventDispatcher().logActionTip(
- LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS);
return true;
}
@@ -187,21 +71,16 @@ public class AllAppsTipView extends AbstractFloatingView {
public static void scheduleShowIfNeeded(Launcher launcher) {
if (!hasSeenAllAppsTip(launcher)) {
- launcher.getStateManager().addStateListener(
- new LauncherStateManager.StateListener() {
- @Override
- public void onStateTransitionStart(LauncherState toState) {
+ launcher.getStateManager().addStateListener(new StateListener() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState == ALL_APPS) {
+ if (showAllAppsTipIfNecessary(launcher)) {
+ launcher.getStateManager().removeStateListener(this);
}
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState == ALL_APPS) {
- if (showAllAppsTipIfNecessary(launcher)) {
- launcher.getStateManager().removeStateListener(this);
- }
- }
- }
- });
+ }
+ }
+ });
}
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
index 425fb13990..914d9e9774 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -16,6 +16,7 @@
package com.android.launcher3.appprediction;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherState.ALL_APPS;
import android.annotation.TargetApi;
@@ -37,18 +38,18 @@ import androidx.core.content.ContextCompat;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.Themes;
/**
* A view which shows a horizontal divider
*/
@TargetApi(Build.VERSION_CODES.O)
-public class AppsDividerView extends View implements LauncherStateManager.StateListener,
+public class AppsDividerView extends View implements StateListener,
FloatingHeaderRow {
private static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
@@ -249,9 +250,6 @@ public class AppsDividerView extends View implements LauncherStateManager.StateL
mLauncher.getStateManager().removeStateListener(this);
}
- @Override
- public void onStateTransitionStart(LauncherState toState) { }
-
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == ALL_APPS) {
@@ -291,7 +289,7 @@ public class AppsDividerView extends View implements LauncherStateManager.StateL
public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
// Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(this, ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
+ setter.setFloat(this, VIEW_ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
index b9f4147f85..d200868899 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
@@ -18,22 +18,17 @@ package com.android.launcher3.appprediction;
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
-import android.content.Context;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.ComponentKey;
public class ComponentKeyMapper {
protected final ComponentKey componentKey;
- private final Context mContext;
private final DynamicItemCache mCache;
- public ComponentKeyMapper(Context context, ComponentKey key, DynamicItemCache cache) {
- mContext = context;
+ public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) {
componentKey = key;
mCache = cache;
}
@@ -61,9 +56,8 @@ public class ComponentKeyMapper {
return item;
} else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) {
return mCache.getInstantApp(componentKey.componentName.getPackageName());
- } else if (componentKey instanceof ShortcutKey) {
- return mCache.getShortcutInfo((ShortcutKey) componentKey);
+ } else {
+ return mCache.getShortcutInfo(componentKey);
}
- return null;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 65e69b6046..ab96b1340a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -18,6 +18,7 @@ package com.android.launcher3.appprediction;
import static android.content.pm.PackageManager.MATCH_INSTANT;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.content.Context;
import android.content.Intent;
@@ -38,11 +39,13 @@ import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import java.util.ArrayList;
@@ -72,8 +75,9 @@ public class DynamicItemCache {
private final Handler mUiHandler;
private final InstantAppResolver mInstantAppResolver;
private final Runnable mOnUpdateCallback;
+ private final IconCache mIconCache;
- private final Map mShortcuts;
+ private final Map mShortcuts;
private final Map mInstantApps;
public DynamicItemCache(Context context, Runnable onUpdateCallback) {
@@ -82,6 +86,7 @@ public class DynamicItemCache {
mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
mInstantAppResolver = InstantAppResolver.newInstance(context);
mOnUpdateCallback = onUpdateCallback;
+ mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
mShortcuts = new HashMap<>();
mInstantApps = new HashMap<>();
@@ -162,21 +167,10 @@ public class DynamicItemCache {
@WorkerThread
private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) {
- DeepShortcutManager mgr = DeepShortcutManager.getInstance(mContext);
- List details = mgr.queryForFullDetails(
- shortcutKey.componentName.getPackageName(),
- Collections.singletonList(shortcutKey.getId()),
- shortcutKey.user);
+ List details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL);
if (!details.isEmpty()) {
WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
- try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
- si.applyFrom(li.createShortcutIcon(details.get(0), true /* badged */, null));
- } catch (Exception e) {
- if (DEBUG) {
- Log.e(TAG, "Error loading shortcut icon for " + shortcutKey.toString());
- }
- return null;
- }
+ mIconCache.getShortcutIcon(si, details.get(0));
return si;
}
if (DEBUG) {
@@ -209,7 +203,7 @@ public class DynamicItemCache {
InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName);
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
iconCache.getTitleAndIcon(info, false);
- if (info.iconBitmap == null || iconCache.isDefaultIcon(info.iconBitmap, info.user)) {
+ if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) {
return null;
}
return info;
@@ -237,7 +231,38 @@ public class DynamicItemCache {
}
@MainThread
- public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) {
+ public WorkspaceItemInfo getShortcutInfo(ComponentKey key) {
return mShortcuts.get(key);
}
+
+ /**
+ * requests and caches icons for app targets
+ */
+ public void updateDependencies(List componentKeyMappers,
+ AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
+ List instantAppsToLoad = new ArrayList<>();
+ List shortcutsToLoad = new ArrayList<>();
+ int total = componentKeyMappers.size();
+ for (int i = 0, count = 0; i < total && count < itemCount; i++) {
+ ComponentKeyMapper mapper = componentKeyMappers.get(i);
+ // Update instant apps
+ if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
+ instantAppsToLoad.add(mapper.getPackage());
+ count++;
+ } else if (mapper.getComponentKey() instanceof ShortcutKey) {
+ shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
+ count++;
+ } else {
+ // Reload high res icon
+ AppInfo info = (AppInfo) mapper.getApp(appsStore);
+ if (info != null) {
+ if (info.usingLowResIcon()) {
+ mIconCache.updateIconInBackground(callback, info);
+ }
+ count++;
+ }
+ }
+ }
+ cacheItems(shortcutsToLoad, instantAppsToLoad);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
index 6e5f4617f4..6c4c601b45 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
@@ -21,9 +21,9 @@ import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKE
import android.content.ComponentName;
import android.content.Intent;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
public class InstantAppItemInfo extends AppInfo {
@@ -44,7 +44,7 @@ public class InstantAppItemInfo extends AppInfo {
workspaceItemInfo.status = WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON
| WorkspaceItemInfo.FLAG_RESTORE_STARTED
| WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI;
- workspaceItemInfo.intent.setPackage(componentName.getPackageName());
+ workspaceItemInfo.getIntent().setPackage(componentName.getPackageName());
return workspaceItemInfo;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index f82af62aac..44691d3bb0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
+/*
+ * Copyright (C) 2012 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.
@@ -16,8 +16,11 @@
package com.android.launcher3.appprediction;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newTarget;
import android.annotation.TargetApi;
import android.content.Context;
@@ -28,6 +31,7 @@ import android.os.Build;
import android.util.AttributeSet;
import android.util.IntProperty;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
@@ -35,17 +39,14 @@ import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
@@ -56,6 +57,10 @@ import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -187,7 +192,7 @@ public class PredictionRowView extends LinearLayout implements
public int getExpectedHeight() {
return getVisibility() == GONE ? 0 :
Launcher.getLauncher(getContext()).getDeviceProfile().allAppsCellHeightPx
- + getPaddingTop() + getPaddingBottom();
+ + getPaddingTop() + getPaddingBottom();
}
@Override
@@ -237,8 +242,9 @@ public class PredictionRowView extends LinearLayout implements
while (getChildCount() > mNumPredictedAppsPerRow) {
removeViewAt(0);
}
+ LayoutInflater inflater = mLauncher.getAppsView().getLayoutInflater();
while (getChildCount() < mNumPredictedAppsPerRow) {
- BubbleTextView icon = (BubbleTextView) mLauncher.getLayoutInflater().inflate(
+ BubbleTextView icon = (BubbleTextView) inflater.inflate(
R.layout.all_apps_icon, this, false);
icon.setOnClickListener(PREDICTION_CLICK_LISTENER);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
@@ -282,7 +288,8 @@ public class PredictionRowView extends LinearLayout implements
mParent.onHeightUpdated();
}
- private List processPredictedAppComponents(List components) {
+ private List processPredictedAppComponents(
+ List components) {
if (getAppsStore().getApps().length == 0) {
// Apps have not been bound yet.
return Collections.emptyList();
@@ -296,7 +303,7 @@ public class PredictionRowView extends LinearLayout implements
predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
predictedApps.add(predictedApp);
} else {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ if (FeatureFlags.IS_STUDIO_BUILD) {
Log.e(TAG, "Predicted app not found: " + mapper);
}
}
@@ -309,16 +316,26 @@ public class PredictionRowView extends LinearLayout implements
}
@Override
- public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
- LauncherLogProto.Target targetParent) {
+ public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
+ ArrayList parents) {
for (int i = 0; i < mPredictedApps.size(); i++) {
ItemInfoWithIcon appInfo = mPredictedApps.get(i);
- if (appInfo == info) {
- targetParent.containerType = LauncherLogProto.ContainerType.PREDICTION;
- target.predictedRank = i;
+ if (appInfo == childInfo) {
+ child.predictedRank = i;
break;
}
}
+ parents.add(newContainerTarget(LauncherLogProto.ContainerType.PREDICTION));
+
+ // include where the prediction is coming this used to be Launcher#modifyUserEvent
+ LauncherLogProto.Target parent = newTarget(LauncherLogProto.Target.Type.CONTAINER);
+ LauncherState state = mLauncher.getStateManager().getState();
+ if (state == LauncherState.ALL_APPS) {
+ parent.containerType = LauncherLogProto.ContainerType.ALLAPPS;
+ } else if (state == OVERVIEW) {
+ parent.containerType = LauncherLogProto.ContainerType.TASKSWITCHER;
+ }
+ parents.add(parent);
}
public void setTextAlpha(int textAlpha) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 9c661074e5..a0f6b044ba 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,11 @@
package com.android.launcher3.appprediction;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
@@ -26,23 +28,23 @@ import android.content.ComponentName;
import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
-import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -50,6 +52,7 @@ import com.android.launcher3.util.MainThreadInitializedObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.OptionalInt;
import java.util.stream.IntStream;
/**
@@ -65,8 +68,8 @@ import java.util.stream.IntStream;
* 4) Maintains the current active client id (for the predictions) and all updates are performed on
* that client id.
*/
-public class PredictionUiStateManager implements StateListener, ItemInfoUpdateReceiver,
- OnIDPChangeListener, OnUpdateListener {
+public class PredictionUiStateManager implements StateListener,
+ ItemInfoUpdateReceiver, OnIDPChangeListener, OnUpdateListener {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
@@ -159,9 +162,6 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) { }
- @Override
- public void onStateTransitionStart(LauncherState toState) { }
-
@Override
public void onStateTransitionComplete(LauncherState state) {
if (mAppsView == null) {
@@ -216,8 +216,11 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
}
private void dispatchOnChange(boolean changed) {
- PredictionState newState = changed ? parseLastState() :
- (mPendingState == null ? mCurrentState : mPendingState);
+ PredictionState newState = changed
+ ? parseLastState()
+ : mPendingState != null && canApplyPredictions(mPendingState)
+ ? mPendingState
+ : mCurrentState;
if (changed && mAppsView != null && !canApplyPredictions(newState)) {
scheduleApplyPredictedApps(newState);
} else {
@@ -245,7 +248,7 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
appTarget.getClassName()), appTarget.getUser());
}
- state.apps.add(new ComponentKeyMapper(mContext, key, mDynamicItemCache));
+ state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
}
updateDependencies(state);
@@ -256,33 +259,8 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
if (!state.isEnabled || mAppsView == null) {
return;
}
-
- IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
- List instantAppsToLoad = new ArrayList<>();
- List shortcutsToLoad = new ArrayList<>();
- int total = state.apps.size();
- for (int i = 0, count = 0; i < total && count < mMaxIconsPerRow; i++) {
- ComponentKeyMapper mapper = state.apps.get(i);
- // Update instant apps
- if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
- instantAppsToLoad.add(mapper.getPackage());
- count++;
- } else if (mapper.getComponentKey() instanceof ShortcutKey) {
- shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
- count++;
- } else {
- // Reload high res icon
- AppInfo info = (AppInfo) mapper.getApp(mAppsView.getAppsStore());
- if (info != null) {
- if (info.usingLowResIcon()) {
- // TODO: Update icon cache to support null callbacks.
- iconCache.updateIconInBackground(this, info);
- }
- count++;
- }
- }
- }
- mDynamicItemCache.cacheItems(shortcutsToLoad, instantAppsToLoad);
+ mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this,
+ mMaxIconsPerRow);
}
@Override
@@ -328,6 +306,32 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
return mCurrentState;
}
+ /**
+ * Returns ranking info for the app within all apps prediction.
+ * Only applicable when {@link ItemInfo#itemType} is one of the followings:
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
+ */
+ public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) {
+ if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) {
+ return OptionalInt.empty();
+ }
+
+ if (itemInfo.itemType == ITEM_TYPE_APPLICATION
+ || itemInfo.itemType == ITEM_TYPE_SHORTCUT
+ || itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
+ ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(),
+ itemInfo.user);
+ final List apps = getCurrentState().apps;
+ return IntStream.range(0, apps.size())
+ .filter(index -> key.equals(apps.get(index).getComponentKey()))
+ .findFirst();
+ }
+
+ return OptionalInt.empty();
+ }
+
/**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is one of the followings:
@@ -337,6 +341,7 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
*/
public static void fillInPredictedRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+
final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
|| (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
@@ -344,12 +349,17 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
return;
}
+ if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) {
+ HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target);
+ return;
+ }
+
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List predictedApps = manager.getCurrentState().apps;
IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst()
- .ifPresent((rank) -> target.predictedRank = rank);
+ .ifPresent((rank) -> target.predictedRank = 0 - rank);
}
public static class PredictionState {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
new file mode 100644
index 0000000000..c968de96a4
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.ActivityTracker;
+
+/**
+ * Proxy activity to return user to home screen and show halfsheet education
+ */
+public class HotseatEduActivity extends Activity {
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ new HotseatActivityTracker<>().addToIntent(homeIntent);
+ startActivity(homeIntent);
+ finish();
+ }
+
+ static class HotseatActivityTracker implements
+ ActivityTracker.SchedulerCallback {
+
+ @Override
+ public boolean init(BaseActivity activity, boolean alreadyOnHome) {
+ QuickstepLauncher launcher = (QuickstepLauncher) activity;
+ if (launcher != null && launcher.getHotseatPredictionController() != null) {
+ launcher.getHotseatPredictionController().showEdu();
+ }
+ return false;
+ }
+
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
new file mode 100644
index 0000000000..4f95254a72
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
+ .LAUNCHER_HOTSEAT_EDU_ONLY_TIP;
+
+import android.content.Intent;
+import android.view.View;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.views.ArrowTipView;
+import com.android.launcher3.views.Snackbar;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+/**
+ * Controller class for managing user onboaridng flow for hybrid hotseat
+ */
+public class HotseatEduController {
+
+ public static final String HOTSEAT_EDU_ACTION =
+ "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
+ public static final String SETTINGS_ACTION =
+ "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
+
+ private final Launcher mLauncher;
+ private final Hotseat mHotseat;
+ private HotseatRestoreHelper mRestoreHelper;
+ private List mPredictedApps;
+ private HotseatEduDialog mActiveDialog;
+
+ private ArrayList mNewItems = new ArrayList<>();
+ private IntArray mNewScreens = null;
+ private Runnable mOnOnboardingComplete;
+
+ HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) {
+ mLauncher = launcher;
+ mHotseat = launcher.getHotseat();
+ mRestoreHelper = restoreHelper;
+ mOnOnboardingComplete = runnable;
+ }
+
+ /**
+ * Checks what type of migration should be used and migrates hotseat
+ */
+ void migrate() {
+ if (mRestoreHelper != null) {
+ mRestoreHelper.createBackup();
+ }
+ if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
+ migrateToFolder();
+ } else {
+ migrateHotseatWhole();
+ }
+ Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled,
+ R.string.hotseat_prediction_settings, null,
+ () -> mLauncher.startActivity(getSettingsIntent()));
+ }
+
+ /**
+ * This migration places all non folder items in the hotseat into a folder and then moves
+ * all folders in the hotseat to a workspace page that has enough empty spots.
+ *
+ * @return pageId that has accepted the items.
+ */
+ private int migrateToFolder() {
+ ArrayDeque folders = new ArrayDeque<>();
+ ArrayList putIntoFolder = new ArrayList<>();
+
+ //separate folders and items that can get in folders
+ for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+ View view = mHotseat.getChildAt(i, 0);
+ if (view == null) continue;
+ ItemInfo info = (ItemInfo) view.getTag();
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ folders.add((FolderInfo) info);
+ } else if (info instanceof WorkspaceItemInfo && info.container == LauncherSettings
+ .Favorites.CONTAINER_HOTSEAT) {
+ putIntoFolder.add((WorkspaceItemInfo) info);
+ }
+ }
+
+ // create a temp folder and add non folder items to it
+ if (!putIntoFolder.isEmpty()) {
+ ItemInfo firstItem = putIntoFolder.get(0);
+ FolderInfo folderInfo = new FolderInfo();
+ mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
+ firstItem.screenId, firstItem.cellX, firstItem.cellY);
+ folderInfo.setTitle("", mLauncher.getModelWriter());
+ folderInfo.contents.addAll(putIntoFolder);
+ for (int i = 0; i < folderInfo.contents.size(); i++) {
+ ItemInfo item = folderInfo.contents.get(i);
+ item.rank = i;
+ mLauncher.getModelWriter().moveItemInDatabase(item, folderInfo.id, 0,
+ item.cellX, item.cellY);
+ }
+ folders.add(folderInfo);
+ }
+ mNewItems.addAll(folders);
+
+ return placeFoldersInWorkspace(folders);
+ }
+
+ private int placeFoldersInWorkspace(ArrayDeque folders) {
+ if (folders.isEmpty()) return 0;
+
+ Workspace workspace = mLauncher.getWorkspace();
+ InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+
+ GridOccupancy[] occupancyList = new GridOccupancy[workspace.getChildCount()];
+ for (int i = 0; i < occupancyList.length; i++) {
+ occupancyList[i] = ((CellLayout) workspace.getChildAt(i)).cloneGridOccupancy();
+ }
+ //scan every screen to find available spots to place folders
+ int occupancyIndex = 0;
+ int[] itemXY = new int[2];
+ while (occupancyIndex < occupancyList.length && !folders.isEmpty()) {
+ GridOccupancy occupancy = occupancyList[occupancyIndex];
+ if (occupancy.findVacantCell(itemXY, 1, 1)) {
+ FolderInfo info = folders.poll();
+ mLauncher.getModelWriter().moveItemInDatabase(info,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ workspace.getScreenIdForPageIndex(occupancyIndex), itemXY[0], itemXY[1]);
+ occupancy.markCells(info, true);
+ } else {
+ occupancyIndex++;
+ }
+ }
+ if (folders.isEmpty()) return workspace.getScreenIdForPageIndex(occupancyIndex);
+ int screenId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ // if all screens are full and we still have folders left, put those on a new page
+ FolderInfo folderInfo;
+ int col = 0;
+ while ((folderInfo = folders.poll()) != null) {
+ mLauncher.getModelWriter().moveItemInDatabase(folderInfo,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, col++,
+ idp.numRows - 1);
+ }
+ mNewScreens = IntArray.wrap(screenId);
+ return workspace.getPageCount();
+ }
+
+ /**
+ * This migration option attempts to move the entire hotseat up to the first workspace that
+ * has space to host items. If no such page is found, it moves items to a new page.
+ *
+ * @return pageId where items are migrated
+ */
+ private int migrateHotseatWhole() {
+ Workspace workspace = mLauncher.getWorkspace();
+
+ int pageId = -1;
+ int toRow = 0;
+ for (int i = 0; i < workspace.getPageCount(); i++) {
+ CellLayout target = workspace.getScreenWithId(workspace.getScreenIdForPageIndex(i));
+ if (target.makeSpaceForHotseatMigration(true)) {
+ toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
+ pageId = i;
+ break;
+ }
+ }
+ if (pageId == -1) {
+ pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ mNewScreens = IntArray.wrap(pageId);
+ }
+ for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+ View child = mHotseat.getChildAt(i, 0);
+ if (child == null || child.getTag() == null) continue;
+ ItemInfo tag = (ItemInfo) child.getTag();
+ if (tag.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) continue;
+ mLauncher.getModelWriter().moveItemInDatabase(tag,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow);
+ mNewItems.add(tag);
+ }
+ return pageId;
+ }
+
+ void moveHotseatItems() {
+ mHotseat.removeAllViewsInLayout();
+ if (!mNewItems.isEmpty()) {
+ int lastPage = mNewItems.get(mNewItems.size() - 1).screenId;
+ ArrayList animated = new ArrayList<>();
+ ArrayList nonAnimated = new ArrayList<>();
+
+ for (ItemInfo info : mNewItems) {
+ if (info.screenId == lastPage) {
+ animated.add(info);
+ } else {
+ nonAnimated.add(info);
+ }
+ }
+ mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated);
+ }
+ }
+
+ void finishOnboarding() {
+ mOnOnboardingComplete.run();
+ }
+
+ void showDimissTip() {
+ if (mHotseat.getShortcutsAndWidgets().getChildCount()
+ < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
+ Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+ R.string.hotseat_prediction_settings, null,
+ () -> mLauncher.startActivity(getSettingsIntent()));
+ } else {
+ new ArrowTipView(mLauncher).show(
+ mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+ }
+ }
+
+ void setPredictedApps(List predictedApps) {
+ mPredictedApps = predictedApps;
+ }
+
+ void showEdu() {
+ int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
+ CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID);
+ // hotseat is already empty and does not require migration. show edu tip
+ boolean requiresMigration = IntStream.range(0, childCount).anyMatch(i -> {
+ View v = mHotseat.getShortcutsAndWidgets().getChildAt(i);
+ return v != null && v.getTag() != null && ((ItemInfo) v.getTag()).container
+ != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ });
+ boolean canMigrateToFirstPage = cellLayout.makeSpaceForHotseatMigration(false);
+ if (requiresMigration && canMigrateToFirstPage) {
+ showDialog();
+ } else {
+ new ArrowTipView(mLauncher).show(mLauncher.getString(
+ requiresMigration ? R.string.hotseat_tip_no_empty_slots
+ : R.string.hotseat_auto_enrolled),
+ mHotseat.getTop());
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP);
+ finishOnboarding();
+ }
+ }
+
+ void showDialog() {
+ if (mPredictedApps == null || mPredictedApps.isEmpty()) {
+ return;
+ }
+ if (mActiveDialog != null) {
+ mActiveDialog.handleClose(false);
+ }
+ mActiveDialog = HotseatEduDialog.getDialog(mLauncher);
+ mActiveDialog.setHotseatEduController(this);
+ mActiveDialog.show(mPredictedApps);
+ }
+
+ static Intent getSettingsIntent() {
+ return new Intent(SETTINGS_ACTION).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+}
+
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
new file mode 100644
index 0000000000..4b8e434d3b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
+ .LAUNCHER_HOTSEAT_EDU_ACCEPT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.views.AbstractSlideInView;
+
+import java.util.List;
+
+/**
+ * User education dialog for hybrid hotseat. Allows user to migrate hotseat items to a new page in
+ * the workspace and shows predictions on the whole hotseat
+ */
+public class HotseatEduDialog extends AbstractSlideInView implements Insettable {
+
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+ protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
+
+ // we use this value to keep track of migration logs as we experiment with different migrations
+ private static final int MIGRATION_EXPERIMENT_IDENTIFIER = 1;
+
+ private final Rect mInsets = new Rect();
+ private View mHotseatWrapper;
+ private CellLayout mSampleHotseat;
+ private Button mDismissBtn;
+
+ public void setHotseatEduController(HotseatEduController hotseatEduController) {
+ mHotseatEduController = hotseatEduController;
+ }
+
+ private HotseatEduController mHotseatEduController;
+
+ public HotseatEduDialog(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public HotseatEduDialog(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContent = this;
+ }
+
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
+ mSampleHotseat = findViewById(R.id.sample_prediction);
+
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ Rect padding = grid.getHotseatLayoutPadding();
+
+ mSampleHotseat.getLayoutParams().height = grid.cellHeightPx;
+ mSampleHotseat.setGridSize(grid.inv.numHotseatIcons, 1);
+ mSampleHotseat.setPadding(padding.left, 0, padding.right, 0);
+
+ Button turnOnBtn = findViewById(R.id.turn_predictions_on);
+ turnOnBtn.setOnClickListener(this::onAccept);
+
+ mDismissBtn = findViewById(R.id.no_thanks);
+ mDismissBtn.setOnClickListener(this::onDismiss);
+
+ // update ui to reflect which migration method is going to be used
+ if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
+ ((TextView) findViewById(R.id.hotseat_edu_content)).setText(
+ R.string.hotseat_edu_message_migrate_alt);
+ }
+ }
+
+ private void onAccept(View v) {
+ mHotseatEduController.migrate();
+ handleClose(true);
+
+ mHotseatEduController.moveHotseatItems();
+ mHotseatEduController.finishOnboarding();
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ACCEPT);
+ }
+
+ private void onDismiss(View v) {
+ mHotseatEduController.showDimissTip();
+ mHotseatEduController.finishOnboarding();
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_DENY);
+ handleClose(true);
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // Since this is on-boarding popup, it is not a user controlled action.
+ }
+
+ @Override
+ public int getLogContainerType() {
+ return LauncherLogProto.ContainerType.TIP;
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ON_BOARD_POPUP) != 0;
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ int leftInset = insets.left - mInsets.left;
+ int rightInset = insets.right - mInsets.right;
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ if (mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+ setPadding(leftInset, getPaddingTop(), rightInset, 0);
+ mHotseatWrapper.setPadding(mHotseatWrapper.getPaddingLeft(), getPaddingTop(),
+ mHotseatWrapper.getPaddingRight(), bottomInset);
+ mHotseatWrapper.getLayoutParams().height =
+ mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
+
+ } else {
+ setPadding(0, getPaddingTop(), 0, 0);
+ mHotseatWrapper.setPadding(mHotseatWrapper.getPaddingLeft(), getPaddingTop(),
+ mHotseatWrapper.getPaddingRight(),
+ (int) getResources().getDimension(R.dimen.bottom_sheet_edu_padding));
+ ((TextView) findViewById(R.id.hotseat_edu_heading)).setText(
+ R.string.hotseat_edu_title_migrate_landscape);
+ ((TextView) findViewById(R.id.hotseat_edu_content)).setText(
+ R.string.hotseat_edu_message_migrate_landscape);
+ }
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mOpenCloseAnimator.start();
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(true, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ handleClose(false);
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return FINAL_SCRIM_BG_COLOR;
+ }
+
+ private void populatePreview(List predictions) {
+ for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+ WorkspaceItemInfo info = predictions.get(i);
+ PredictedAppIcon icon = PredictedAppIcon.createIcon(mSampleHotseat, info);
+ icon.setEnabled(false);
+ icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ icon.verifyHighRes();
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(i, 0, 1, 1);
+ mSampleHotseat.addViewToCellLayout(icon, i, info.getViewId(), lp, true);
+ }
+ }
+
+ /**
+ * Opens User education dialog with a list of suggested apps
+ */
+ public void show(List predictions) {
+ if (getParent() != null
+ || predictions.size() < mLauncher.getDeviceProfile().inv.numHotseatIcons
+ || mHotseatEduController == null) {
+ return;
+ }
+ AbstractFloatingView.closeAllOpenViews(mLauncher);
+ attachToContainer();
+ mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN);
+ animateOpen();
+ populatePreview(predictions);
+ }
+
+ /**
+ * Factory method for HotseatPredictionUserEdu dialog
+ */
+ public static HotseatEduDialog getDialog(Launcher launcher) {
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ return (HotseatEduDialog) layoutInflater.inflate(
+ R.layout.predicted_hotseat_edu, launcher.getDragLayer(),
+ false);
+
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
new file mode 100644
index 0000000000..c15a5963f6
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Helper class to allow hot seat file logging
+ */
+public class HotseatFileLog {
+
+ public static final int LOG_DAYS = 10;
+ private static final String FILE_NAME_PREFIX = "hotseat-log-";
+ private static final DateFormat DATE_FORMAT =
+ DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+ public static final MainThreadInitializedObject INSTANCE =
+ new MainThreadInitializedObject<>(HotseatFileLog::new);
+
+
+ private final Handler mHandler = new Handler(
+ Executors.createAndStartNewLooper("hotseat-logger"));
+ private final File mLogsDir;
+ private PrintWriter mCurrentWriter;
+ private String mFileName;
+
+ private HotseatFileLog(Context context) {
+ mLogsDir = context.getFilesDir();
+ }
+
+ /**
+ * Prints log values to disk
+ */
+ public void log(String tag, String msg) {
+ String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg);
+
+ mHandler.post(() -> {
+ synchronized (this) {
+ PrintWriter writer = getWriter();
+ if (writer != null) {
+ writer.println(out);
+ }
+ }
+ });
+ }
+
+ private PrintWriter getWriter() {
+ String fName = FILE_NAME_PREFIX + (LOG_DAYS % 10);
+ if (fName.equals(mFileName)) return mCurrentWriter;
+
+ Calendar cal = Calendar.getInstance();
+
+ boolean append = false;
+ File logFile = new File(mLogsDir, fName);
+ if (logFile.exists()) {
+ Calendar modifiedTime = Calendar.getInstance();
+ modifiedTime.setTimeInMillis(logFile.lastModified());
+
+ // If the file was modified more that 36 hours ago, purge the file.
+ // We use instead of 24 to account for day-365 followed by day-1
+ modifiedTime.add(Calendar.HOUR, 36);
+ append = cal.before(modifiedTime);
+ }
+
+
+ if (mCurrentWriter != null) {
+ mCurrentWriter.close();
+ }
+ try {
+ mCurrentWriter = new PrintWriter(new FileWriter(logFile, append));
+ mFileName = fName;
+ } catch (Exception ex) {
+ Log.e("HotseatLogs", "Error writing logs to file", ex);
+ closeWriter();
+ }
+ return mCurrentWriter;
+ }
+
+
+ private synchronized void closeWriter() {
+ mFileName = null;
+ if (mCurrentWriter != null) {
+ mCurrentWriter.close();
+ }
+ mCurrentWriter = null;
+ }
+
+
+ /**
+ * Returns a list of all log files
+ */
+ public synchronized File[] getLogFiles() {
+ File[] files = new File[LOG_DAYS + FileLog.LOG_DAYS];
+ //include file log files here
+ System.arraycopy(FileLog.getLogFiles(), 0, files, 0, FileLog.LOG_DAYS);
+
+ closeWriter();
+ for (int i = 0; i < LOG_DAYS; i++) {
+ files[FileLog.LOG_DAYS + i] = new File(mLogsDir, FILE_NAME_PREFIX + i);
+ }
+ return files;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
new file mode 100644
index 0000000000..b94e6337d0
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -0,0 +1,723 @@
+/*
+ * 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.hybridhotseat;
+
+import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.content.ComponentName;
+import android.os.Process;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.appprediction.ComponentKeyMapper;
+import com.android.launcher3.appprediction.DynamicItemCache;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.views.ArrowTipView;
+import com.android.launcher3.views.Snackbar;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+/**
+ * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
+ * pinning of predicted apps and manages replacement of predicted apps with user drag.
+ */
+public class HotseatPredictionController implements DragController.DragListener,
+ View.OnAttachStateChangeListener, SystemShortcut.Factory,
+ InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
+ IconCache.ItemInfoUpdateReceiver, DragSource {
+
+ private static final String TAG = "PredictiveHotseat";
+ private static final boolean DEBUG = false;
+
+ private static final String PREDICTION_CLIENT = "hotseat";
+ private DropTarget.DragObject mDragObject;
+ private int mHotSeatItemsCount;
+ private int mPredictedSpotsCount = 0;
+
+ private Launcher mLauncher;
+ private final Hotseat mHotseat;
+
+ private final HotseatRestoreHelper mRestoreHelper;
+
+ private List mComponentKeyMappers = new ArrayList<>();
+
+ private DynamicItemCache mDynamicItemCache;
+
+ private final HotseatPredictionModel mPredictionModel;
+ private AppPredictor mAppPredictor;
+ private AllAppsStore mAllAppsStore;
+ private AnimatorSet mIconRemoveAnimators;
+ private boolean mUIUpdatePaused = false;
+ private boolean mIsDestroyed = false;
+
+
+ private List mOutlineDrawings = new ArrayList<>();
+
+ private final View.OnLongClickListener mPredictionLongClickListener = v -> {
+ if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+ if (mLauncher.getWorkspace().isSwitchingState()) return false;
+ if (!mLauncher.getOnboardingPrefs().getBoolean(
+ OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
+ Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+ R.string.hotseat_prediction_settings, null,
+ () -> mLauncher.startActivity(getSettingsIntent()));
+ mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
+ mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ return true;
+ }
+ // Start the drag
+ mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
+ return true;
+ };
+
+ public HotseatPredictionController(Launcher launcher) {
+ mLauncher = launcher;
+ mHotseat = launcher.getHotseat();
+ mAllAppsStore = mLauncher.getAppsView().getAppsStore();
+ LauncherAppState appState = LauncherAppState.getInstance(launcher);
+ mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel();
+ mAllAppsStore.addUpdateListener(this);
+ mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
+ mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
+ launcher.getDeviceProfile().inv.addOnChangeListener(this);
+ mHotseat.addOnAttachStateChangeListener(this);
+ mRestoreHelper = new HotseatRestoreHelper(mLauncher);
+ if (mHotseat.isAttachedToWindow()) {
+ onViewAttachedToWindow(mHotseat);
+ }
+ }
+
+ /**
+ * Shows appropriate hotseat education based on prediction enabled and migration states.
+ */
+ public void showEdu() {
+ mLauncher.getStateManager().goToState(NORMAL, true, () -> {
+ if (mComponentKeyMappers.isEmpty()) {
+ // launcher has empty predictions set
+ Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
+ R.string.hotseat_prediction_settings, null,
+ () -> mLauncher.startActivity(getSettingsIntent()));
+ } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
+ showDiscoveryTip();
+ } else {
+ HotseatEduController eduController = new HotseatEduController(mLauncher,
+ mRestoreHelper,
+ this::createPredictor);
+ eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
+ eduController.showEdu();
+ }
+ });
+ }
+
+ /**
+ * Shows educational tip for hotseat if user does not go through Tips app.
+ */
+ private void showDiscoveryTip() {
+ if (getPredictedIcons().isEmpty()) {
+ new ArrowTipView(mLauncher).show(
+ mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+ } else {
+ Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+ R.string.hotseat_prediction_settings, null,
+ () -> mLauncher.startActivity(getSettingsIntent()));
+ }
+ }
+
+ /**
+ * Returns if hotseat client has predictions
+ */
+ public boolean hasPredictions() {
+ return !mComponentKeyMappers.isEmpty();
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ mLauncher.getDragController().addDragListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ mLauncher.getDragController().removeDragListener(this);
+ }
+
+ private void fillGapsWithPrediction() {
+ fillGapsWithPrediction(false, null);
+ }
+
+ private void fillGapsWithPrediction(boolean animate, Runnable callback) {
+ if (mUIUpdatePaused || mDragObject != null) {
+ return;
+ }
+ List predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
+ if (mComponentKeyMappers.isEmpty() != predictedApps.isEmpty()) {
+ // Safely ignore update as AppsList is not ready yet. This will called again once
+ // apps are ready (HotseatPredictionController#onAppsUpdated)
+ return;
+ }
+ int predictionIndex = 0;
+ ArrayList newItems = new ArrayList<>();
+ // make sure predicted icon removal and filling predictions don't step on each other
+ if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
+ mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ fillGapsWithPrediction(animate, callback);
+ mIconRemoveAnimators.removeListener(this);
+ }
+ });
+ return;
+ }
+ for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
+ View child = mHotseat.getChildAt(
+ mHotseat.getCellXFromOrder(rank),
+ mHotseat.getCellYFromOrder(rank));
+
+ if (child != null && !isPredictedIcon(child)) {
+ continue;
+ }
+ if (predictedApps.size() <= predictionIndex) {
+ // Remove predicted apps from the past
+ if (isPredictedIcon(child)) {
+ mHotseat.removeView(child);
+ }
+ continue;
+ }
+ WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
+ if (isPredictedIcon(child) && child.isEnabled()) {
+ PredictedAppIcon icon = (PredictedAppIcon) child;
+ icon.applyFromWorkspaceItem(predictedItem);
+ icon.finishBinding(mPredictionLongClickListener);
+ } else {
+ newItems.add(predictedItem);
+ }
+ preparePredictionInfo(predictedItem, rank);
+ }
+ mPredictedSpotsCount = predictionIndex;
+ bindItems(newItems, animate, callback);
+ }
+
+ private void bindItems(List itemsToAdd, boolean animate, Runnable callback) {
+ AnimatorSet animationSet = new AnimatorSet();
+ for (WorkspaceItemInfo item : itemsToAdd) {
+ PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
+ mLauncher.getWorkspace().addInScreenFromBind(icon, item);
+ icon.finishBinding(mPredictionLongClickListener);
+ if (animate) {
+ animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
+ }
+ }
+ if (animate) {
+ if (callback != null) {
+ animationSet.addListener(AnimationSuccessListener.forRunnable(callback));
+ }
+ animationSet.start();
+ } else {
+ if (callback != null) callback.run();
+ }
+ }
+
+ /**
+ * Unregisters callbacks and frees resources
+ */
+ public void destroy() {
+ mIsDestroyed = true;
+ mAllAppsStore.removeUpdateListener(this);
+ mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
+ mHotseat.removeOnAttachStateChangeListener(this);
+ if (mAppPredictor != null) {
+ mAppPredictor.destroy();
+ }
+ }
+
+ /**
+ * start and pauses predicted apps update on the hotseat
+ */
+ public void setPauseUIUpdate(boolean paused) {
+ mUIUpdatePaused = paused;
+ if (!paused) {
+ fillGapsWithPrediction();
+ }
+ }
+
+ /**
+ * Creates App Predictor with all the current apps pinned on the hotseat
+ */
+ public void createPredictor() {
+ AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
+ if (apm == null) {
+ return;
+ }
+ if (mAppPredictor != null) {
+ mAppPredictor.destroy();
+ mAppPredictor = null;
+ }
+ WeakReference controllerRef = new WeakReference<>(this);
+
+
+ mPredictionModel.createBundle(bundle -> {
+ if (mIsDestroyed) return;
+ mAppPredictor = apm.createAppPredictionSession(
+ new AppPredictionContext.Builder(mLauncher)
+ .setUiSurface(PREDICTION_CLIENT)
+ .setPredictedTargetCount(mHotSeatItemsCount)
+ .setExtras(bundle)
+ .build());
+ mAppPredictor.registerPredictionUpdates(
+ mLauncher.getApplicationContext().getMainExecutor(),
+ list -> {
+ if (controllerRef.get() != null) {
+ controllerRef.get().setPredictedApps(list);
+ }
+ });
+ mAppPredictor.requestPredictionUpdate();
+ });
+ setPauseUIUpdate(false);
+ }
+
+ /**
+ * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items.
+ */
+ public void showCachedItems(List apps, IntArray ranks) {
+ if (hasPredictions() && mAppPredictor != null) {
+ mAppPredictor.requestPredictionUpdate();
+ fillGapsWithPrediction();
+ return;
+ }
+ int count = Math.min(ranks.size(), apps.size());
+ List items = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i));
+ ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user);
+ preparePredictionInfo(item, ranks.get(i));
+ items.add(item);
+
+ mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache));
+ }
+ updateDependencies();
+ bindItems(items, false, null);
+ }
+
+ private void setPredictedApps(List appTargets) {
+ mComponentKeyMappers.clear();
+ if (appTargets.isEmpty()) {
+ mRestoreHelper.restoreBackup();
+ }
+ StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
+ ArrayList componentKeys = new ArrayList<>();
+ for (AppTarget appTarget : appTargets) {
+ ComponentKey key;
+ if (appTarget.getShortcutInfo() != null) {
+ key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
+ } else {
+ key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
+ appTarget.getClassName()), appTarget.getUser());
+ }
+ componentKeys.add(key);
+ predictionLog.append(key.toString());
+ predictionLog.append(",rank:");
+ predictionLog.append(appTarget.getRank());
+ predictionLog.append("\n");
+ mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
+ }
+ predictionLog.append("]");
+ if (Utilities.IS_DEBUG_DEVICE) {
+ HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString());
+ }
+ updateDependencies();
+ fillGapsWithPrediction();
+ mPredictionModel.cachePredictionComponentKeys(componentKeys);
+ }
+
+ private void updateDependencies() {
+ mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
+ mHotSeatItemsCount);
+ }
+
+ /**
+ * Pins a predicted app icon into place.
+ */
+ public void pinPrediction(ItemInfo info) {
+ PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
+ mHotseat.getCellXFromOrder(info.rank),
+ mHotseat.getCellYFromOrder(info.rank));
+ if (icon == null) {
+ return;
+ }
+ WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
+ mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
+ workspaceItemInfo.cellX, workspaceItemInfo.cellY);
+ ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
+ icon.pin(workspaceItemInfo);
+ AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo);
+ if (appTarget != null) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
+ AppTargetEvent.ACTION_PIN, workspaceItemInfo));
+ }
+ }
+
+ private List mapToWorkspaceItemInfo(
+ List components) {
+ AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
+ if (allAppsStore.getApps().length == 0) {
+ return Collections.emptyList();
+ }
+
+ List predictedApps = new ArrayList<>();
+ for (ComponentKeyMapper mapper : components) {
+ ItemInfoWithIcon info = mapper.getApp(allAppsStore);
+ if (info instanceof AppInfo) {
+ WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info);
+ predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ predictedApps.add(predictedApp);
+ } else if (info instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((WorkspaceItemInfo) info);
+ predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ predictedApps.add(predictedApp);
+ } else {
+ if (DEBUG) {
+ Log.e(TAG, "Predicted app not found: " + mapper);
+ }
+ }
+ // Stop at the number of hotseat items
+ if (predictedApps.size() == mHotSeatItemsCount) {
+ break;
+ }
+ }
+ return predictedApps;
+ }
+
+ private List getPredictedIcons() {
+ List icons = new ArrayList<>();
+ ViewGroup vg = mHotseat.getShortcutsAndWidgets();
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ View child = vg.getChildAt(i);
+ if (isPredictedIcon(child)) {
+ icons.add((PredictedAppIcon) child);
+ }
+ }
+ return icons;
+ }
+
+ private void removePredictedApps(List outlines,
+ ItemInfo draggedInfo) {
+ if (mIconRemoveAnimators != null) {
+ mIconRemoveAnimators.end();
+ }
+ mIconRemoveAnimators = new AnimatorSet();
+ removeOutlineDrawings();
+ for (PredictedAppIcon icon : getPredictedIcons()) {
+ if (!icon.isEnabled()) {
+ continue;
+ }
+ if (icon.getTag().equals(draggedInfo)) {
+ mHotseat.removeView(icon);
+ continue;
+ }
+ int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
+ outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
+ mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
+ icon.setEnabled(false);
+ ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
+ animator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (icon.getParent() != null) {
+ mHotseat.removeView(icon);
+ }
+ }
+ });
+ mIconRemoveAnimators.play(animator);
+ }
+ mIconRemoveAnimators.start();
+ }
+
+ private void notifyItemAction(AppTargetEvent event) {
+ if (mAppPredictor != null) {
+ mAppPredictor.notifyAppTargetEvent(event);
+ }
+ }
+
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ removePredictedApps(mOutlineDrawings, dragObject.dragInfo);
+ mDragObject = dragObject;
+ if (mOutlineDrawings.isEmpty()) return;
+ for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+ mHotseat.addDelegatedCellDrawing(outlineDrawing);
+ }
+ mHotseat.invalidate();
+ }
+
+ /**
+ * Unpins pinned app when it's converted into a folder
+ */
+ public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
+ AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
+ AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
+ if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
+ AppTargetEvent.ACTION_PIN, folderInfo));
+ }
+ // using folder info with isTrackedForPrediction as itemInfo.container is already changed
+ // to folder by this point
+ if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
+ AppTargetEvent.ACTION_UNPIN, folderInfo
+ ));
+ }
+ }
+
+ /**
+ * Pins workspace item created when all folder items are removed but one
+ */
+ public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
+ AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
+ AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
+ if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
+ AppTargetEvent.ACTION_UNPIN, folderInfo));
+ }
+ if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
+ AppTargetEvent.ACTION_PIN, itemInfo));
+ }
+ }
+
+ @Override
+ public void onDragEnd() {
+ if (mDragObject == null) {
+ return;
+ }
+
+ ItemInfo dragInfo = mDragObject.dragInfo;
+ if (mDragObject.isMoved()) {
+ AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo);
+ //always send pin event first to prevent AiAi from predicting an item moved within
+ // the same page
+ if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
+ AppTargetEvent.ACTION_PIN, dragInfo));
+ }
+ if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(
+ mDragObject.originalDragInfo)) {
+ notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
+ AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo));
+ }
+ }
+ mDragObject = null;
+ fillGapsWithPrediction(true, this::removeOutlineDrawings);
+ }
+
+
+ @Nullable
+ @Override
+ public SystemShortcut getShortcut(QuickstepLauncher activity,
+ ItemInfo itemInfo) {
+ if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ return null;
+ }
+ return new PinPrediction(activity, itemInfo);
+ }
+
+ private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
+ itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ itemInfo.rank = rank;
+ itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
+ itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
+ itemInfo.screenId = rank;
+ }
+
+ private void removeOutlineDrawings() {
+ if (mOutlineDrawings.isEmpty()) return;
+ for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+ mHotseat.removeDelegatedCellDrawing(outlineDrawing);
+ }
+ mHotseat.invalidate();
+ mOutlineDrawings.clear();
+ }
+
+ @Override
+ public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
+ if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
+ this.mHotSeatItemsCount = profile.numHotseatIcons;
+ createPredictor();
+ }
+ }
+
+ @Override
+ public void onAppsUpdated() {
+ fillGapsWithPrediction();
+ }
+
+ @Override
+ public void reapplyItemInfo(ItemInfoWithIcon info) {
+ }
+
+ @Override
+ public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
+ //Does nothing
+ }
+
+ @Override
+ public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
+ ArrayList parents) {
+ mHotseat.fillInLogContainerData(childInfo, child, parents);
+ }
+
+ /**
+ * Logs rank info based on current list of predicted items
+ */
+ public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
+ if (Utilities.IS_DEBUG_DEVICE) {
+ final String pkg = itemInfo.getTargetComponent() != null
+ ? itemInfo.getTargetComponent().getPackageName() : "unknown";
+ HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
+ "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
+ && !Process.myUserHandle().equals(itemInfo.user))
+ + ",launchLocation:" + itemInfo.container);
+ }
+
+ if (itemInfo.getTargetComponent() == null || itemInfo.user == null) {
+ return;
+ }
+
+ final ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+
+ final List predictedApps = new ArrayList<>(mComponentKeyMappers);
+ OptionalInt rank = IntStream.range(0, predictedApps.size())
+ .filter(index -> key.equals(predictedApps.get(index).getComponentKey()))
+ .findFirst();
+ if (!rank.isPresent()) {
+ return;
+ }
+
+ int cardinality = 0;
+ for (PredictedAppIcon icon : getPredictedIcons()) {
+ ItemInfo info = (ItemInfo) icon.getTag();
+ cardinality |= 1 << info.screenId;
+ }
+
+ PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder();
+ containerBuilder.setCardinality(cardinality);
+ if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ containerBuilder.setIndex(rank.getAsInt());
+ }
+ mLauncher.getStatsLogManager().logger()
+ .withInstanceId(instanceId)
+ .withRank(rank.getAsInt())
+ .withContainerInfo(ContainerInfo.newBuilder()
+ .setPredictedHotseatContainer(containerBuilder)
+ .build())
+ .log(LAUNCHER_HOTSEAT_RANKED);
+ }
+
+ private class PinPrediction extends SystemShortcut {
+
+ private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
+ super(R.drawable.ic_pin, R.string.pin_prediction, target,
+ itemInfo);
+ }
+
+ @Override
+ public void onClick(View view) {
+ dismissTaskMenuView(mTarget);
+ pinPrediction(mItemInfo);
+ }
+ }
+
+ /**
+ * Fill in predicted_rank field based on app prediction.
+ * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
+ */
+ public static void encodeHotseatLayoutIntoPredictionRank(
+ @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+ QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
+ if (launcher == null || launcher.getHotseatPredictionController() == null
+ || itemInfo.getTargetComponent() == null) {
+ return;
+ }
+ HotseatPredictionController controller = launcher.getHotseatPredictionController();
+
+ final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+
+ final List predictedApps = controller.mComponentKeyMappers;
+ OptionalInt rank = IntStream.range(0, predictedApps.size())
+ .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+ .findFirst();
+
+ target.predictedRank = 10000 + (controller.mPredictedSpotsCount * 100)
+ + (rank.isPresent() ? rank.getAsInt() + 1 : 0);
+ }
+
+ private static boolean isPredictedIcon(View view) {
+ return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) view.getTag()).container
+ == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
new file mode 100644
index 0000000000..5a038d27af
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.PredictionModel;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+/**
+ * Model helper for app predictions in workspace
+ */
+public class HotseatPredictionModel extends PredictionModel {
+ private static final String APP_LOCATION_HOTSEAT = "hotseat";
+ private static final String APP_LOCATION_WORKSPACE = "workspace";
+
+ private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events";
+ private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items";
+
+
+ public HotseatPredictionModel(Context context) { }
+
+ /**
+ * Creates and returns bundle using workspace items and cached items
+ */
+ public void createBundle(Consumer cb) {
+ LauncherAppState appState = LauncherAppState.getInstance(mContext);
+ appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ Bundle bundle = new Bundle();
+ ArrayList events = new ArrayList<>();
+ ArrayList workspaceItems = new ArrayList<>(dataModel.workspaceItems);
+ workspaceItems.addAll(dataModel.appWidgets);
+ for (ItemInfo item : workspaceItems) {
+ AppTarget target = getAppTargetFromInfo(item);
+ if (target != null && !isTrackedForPrediction(item)) continue;
+ events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
+ }
+ ArrayList currentTargets = new ArrayList<>();
+ for (ItemInfo itemInfo : dataModel.cachedPredictedItems) {
+ AppTarget target = getAppTargetFromInfo(itemInfo);
+ if (target != null) currentTargets.add(target);
+ }
+ bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
+ bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
+ MAIN_EXECUTOR.execute(() -> cb.accept(bundle));
+ }
+ });
+ }
+
+ /**
+ * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null
+ * if item is not supported prediction
+ */
+ public AppTarget getAppTargetFromInfo(ItemInfo info) {
+ if (info == null) return null;
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+ && info instanceof LauncherAppWidgetInfo
+ && ((LauncherAppWidgetInfo) info).providerName != null) {
+ ComponentName cn = ((LauncherAppWidgetInfo) info).providerName;
+ return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()),
+ cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && info.getTargetComponent() != null) {
+ ComponentName cn = info.getTargetComponent();
+ return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
+ cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && info instanceof WorkspaceItemInfo) {
+ ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info);
+ //TODO: switch to using full shortcut info
+ return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()),
+ shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
+ mContext.getPackageName(), info.user).build();
+ }
+ return null;
+ }
+
+
+ /**
+ * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
+ * location using {@link ItemInfo}
+ */
+ public AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) {
+ String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
+ info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
+ info.screenId, info.cellX, info.cellY, info.spanX, info.spanY);
+ return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build();
+ }
+
+ /**
+ * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
+ */
+ public static boolean isTrackedForPrediction(ItemInfo info) {
+ return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || (
+ info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
+ && info.screenId == Workspace.FIRST_SCREEN_ID);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
new file mode 100644
index 0000000000..8c1db4e2e7
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import static com.android.launcher3.LauncherSettings.Favorites.HYBRID_HOTSEAT_BACKUP_TABLE;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.GridBackupTable;
+import com.android.launcher3.provider.LauncherDbUtils;
+
+/**
+ * A helper class to manage migration revert restoration for hybrid hotseat
+ */
+public class HotseatRestoreHelper {
+ private final Launcher mLauncher;
+ private boolean mBackupRestored = false;
+
+ HotseatRestoreHelper(Launcher context) {
+ mLauncher = context;
+ }
+
+ /**
+ * Creates a snapshot backup of Favorite table for future restoration use.
+ */
+ public void createBackup() {
+ MODEL_EXECUTOR.execute(() -> {
+ try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
+ LauncherSettings.Settings.call(
+ mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
+ .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+ InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+ GridBackupTable backupTable = new GridBackupTable(mLauncher,
+ transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
+ idp.numRows);
+ backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
+ transaction.commit();
+ LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
+ }
+ });
+ }
+
+ /**
+ * Finds and restores a previously saved snapshow of Favorites table
+ */
+ public void restoreBackup() {
+ if (mBackupRestored) return;
+ MODEL_EXECUTOR.execute(() -> {
+ try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
+ LauncherSettings.Settings.call(
+ mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
+ .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+ if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
+ return;
+ }
+ InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+ GridBackupTable backupTable = new GridBackupTable(mLauncher,
+ transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
+ idp.numRows);
+ backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
+ transaction.commit();
+ mBackupRestored = true;
+ mLauncher.getModel().forceReload();
+ }
+ });
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
new file mode 100644
index 0000000000..597c17b4e2
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -0,0 +1,283 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.PIN_PREDICTION;
+import static com.android.launcher3.graphics.IconShape.getShape;
+
+import android.content.Context;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.os.Process;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.core.graphics.ColorUtils;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
+import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.DoubleShadowBubbleTextView;
+
+/**
+ * A BubbleTextView with a ring around it's drawable
+ */
+public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
+ LauncherAccessibilityDelegate.AccessibilityActionHandler {
+
+ private static final int RING_SHADOW_COLOR = 0x99000000;
+ private static final float RING_EFFECT_RATIO = 0.095f;
+
+ boolean mIsDrawingDot = false;
+ private final DeviceProfile mDeviceProfile;
+ private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Path mRingPath = new Path();
+ private boolean mIsPinned = false;
+ private final int mNormalizedIconRadius;
+ private final BlurMaskFilter mShadowFilter;
+ private int mPlateColor;
+ boolean mDrawForDrag = false;
+
+ public PredictedAppIcon(Context context) {
+ this(context, null, 0);
+ }
+
+ public PredictedAppIcon(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mDeviceProfile = ActivityContext.lookupContext(context).getDeviceProfile();
+ mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2;
+ int shadowSize = context.getResources().getDimensionPixelSize(
+ R.dimen.blur_size_thin_outline);
+ mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ int count = canvas.save();
+ if (!mIsPinned) {
+ boolean isBadged = getTag() instanceof WorkspaceItemInfo
+ && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
+ drawEffect(canvas, isBadged);
+ canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
+ canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
+ }
+ super.onDraw(canvas);
+ canvas.restoreToCount(count);
+ }
+
+ @Override
+ protected void drawDotIfNecessary(Canvas canvas) {
+ mIsDrawingDot = true;
+ int count = canvas.save();
+ canvas.translate(-getWidth() * RING_EFFECT_RATIO, -getHeight() * RING_EFFECT_RATIO);
+ canvas.scale(1 + 2 * RING_EFFECT_RATIO, 1 + 2 * RING_EFFECT_RATIO);
+ super.drawDotIfNecessary(canvas);
+ canvas.restoreToCount(count);
+ mIsDrawingDot = false;
+ }
+
+ @Override
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
+ super.applyFromWorkspaceItem(info);
+ int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
+ mPlateColor = ColorUtils.setAlphaComponent(color, 200);
+ if (mIsPinned) {
+ setContentDescription(info.contentDescription);
+ } else {
+ setContentDescription(
+ getContext().getString(R.string.hotseat_prediction_content_description,
+ info.contentDescription));
+ }
+ }
+
+ /**
+ * Removes prediction ring from app icon
+ */
+ public void pin(WorkspaceItemInfo info) {
+ if (mIsPinned) return;
+ mIsPinned = true;
+ applyFromWorkspaceItem(info);
+ setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+ ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
+ invalidate();
+ }
+
+ /**
+ * prepares prediction icon for usage after bind
+ */
+ public void finishBinding(OnLongClickListener longClickListener) {
+ setOnLongClickListener(longClickListener);
+ ((CellLayout.LayoutParams) getLayoutParams()).canReorder = false;
+ setTextVisibility(false);
+ verifyHighRes();
+ }
+
+ @Override
+ public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) {
+ if (!mIsPinned) {
+ accessibilityNodeInfo.addAction(
+ new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
+ getContext().getText(R.string.pin_prediction)));
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, ItemInfo info) {
+ QuickstepLauncher launcher = Launcher.cast(Launcher.getLauncher(getContext()));
+ if (action == PIN_PREDICTION) {
+ if (launcher == null || launcher.getHotseatPredictionController() == null) {
+ return false;
+ }
+ HotseatPredictionController controller = launcher.getHotseatPredictionController();
+ controller.pinPrediction(info);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void getIconBounds(Rect outBounds) {
+ super.getIconBounds(outBounds);
+ if (!mIsPinned && !mIsDrawingDot) {
+ int predictionInset = (int) (getIconSize() * RING_EFFECT_RATIO);
+ outBounds.inset(predictionInset, predictionInset);
+ }
+ }
+
+ private int getOutlineOffsetX() {
+ return (getMeasuredWidth() / 2) - mNormalizedIconRadius;
+ }
+
+ private int getOutlineOffsetY() {
+ return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+ }
+
+ private void drawEffect(Canvas canvas, boolean isBadged) {
+ // Don't draw ring effect if item is about to be dragged.
+ if (mDrawForDrag) {
+ return;
+ }
+ mRingPath.reset();
+ getShape().addToPath(mRingPath, getOutlineOffsetX(), getOutlineOffsetY(),
+ mNormalizedIconRadius);
+ if (isBadged) {
+ float outlineSize = mNormalizedIconRadius * RING_EFFECT_RATIO * 2;
+ float iconSize = getIconSize() * (1 - 2 * RING_EFFECT_RATIO);
+ float badgeSize = LauncherIcons.getBadgeSizeForIconSize((int) iconSize) + outlineSize;
+ float badgeInset = mNormalizedIconRadius * 2 - badgeSize;
+ getShape().addToPath(mRingPath, getOutlineOffsetX() + badgeInset,
+ getOutlineOffsetY() + badgeInset, badgeSize / 2);
+
+ }
+ mIconRingPaint.setColor(RING_SHADOW_COLOR);
+ mIconRingPaint.setMaskFilter(mShadowFilter);
+ canvas.drawPath(mRingPath, mIconRingPaint);
+ mIconRingPaint.setColor(mPlateColor);
+ mIconRingPaint.setMaskFilter(null);
+ canvas.drawPath(mRingPath, mIconRingPaint);
+ }
+
+ @Override
+ public void getSourceVisualDragBounds(Rect bounds) {
+ super.getSourceVisualDragBounds(bounds);
+ if (!mIsPinned) {
+ int internalSize = (int) (bounds.width() * RING_EFFECT_RATIO);
+ bounds.inset(internalSize, internalSize);
+ }
+ }
+
+ @Override
+ public SafeCloseable prepareDrawDragView() {
+ mDrawForDrag = true;
+ invalidate();
+ SafeCloseable r = super.prepareDrawDragView();
+ return () -> {
+ r.close();
+ mDrawForDrag = false;
+ };
+ }
+
+ /**
+ * Creates and returns a new instance of PredictedAppIcon from WorkspaceItemInfo
+ */
+ public static PredictedAppIcon createIcon(ViewGroup parent, WorkspaceItemInfo info) {
+ PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.predicted_app_icon, parent, false);
+ icon.applyFromWorkspaceItem(info);
+ icon.setOnClickListener(ItemClickHandler.INSTANCE);
+ icon.setOnFocusChangeListener(Launcher.getLauncher(parent.getContext()).getFocusHandler());
+ return icon;
+ }
+
+ /**
+ * Draws Predicted Icon outline on cell layout
+ */
+ public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
+
+ private int mOffsetX;
+ private int mOffsetY;
+ private int mIconRadius;
+ private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) {
+ mDelegateCellX = cellX;
+ mDelegateCellY = cellY;
+ mOffsetX = icon.getOutlineOffsetX();
+ mOffsetY = icon.getOutlineOffsetY();
+ mIconRadius = icon.mNormalizedIconRadius;
+ mOutlinePaint.setStyle(Paint.Style.FILL);
+ mOutlinePaint.setColor(Color.argb(24, 245, 245, 245));
+ }
+
+ /**
+ * Draws predicted app icon outline under CellLayout
+ */
+ @Override
+ public void drawUnderItem(Canvas canvas) {
+ getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint);
+ }
+
+ /**
+ * Draws PredictedAppIcon outline over CellLayout
+ */
+ @Override
+ public void drawOverItem(Canvas canvas) {
+ // Does nothing
+ }
+ }
+}
diff --git a/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
similarity index 56%
rename from go/quickstep/src/com/android/quickstep/IconRecentsFragment.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
index facf0d22c2..8f1d3197cd 100644
--- a/go/quickstep/src/com/android/quickstep/IconRecentsFragment.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -13,25 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.launcher3.uioverrides;
-package com.android.quickstep;
-
-import android.app.Fragment;
-import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.R;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
-public class IconRecentsFragment extends Fragment {
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- return inflater.inflate(R.layout.icon_recents_root_view, container, false);
+/** A util class that inflates a predicted app icon */
+public class PredictedAppIconInflater {
+ public static View inflate(LayoutInflater inflater, ViewGroup parent, WorkspaceItemInfo info) {
+ PredictedAppIcon icon = (PredictedAppIcon) inflater.inflate(
+ R.layout.predicted_app_icon, parent, false);
+ icon.applyFromWorkspaceItem(info);
+ return icon;
}
-}
\ No newline at end of file
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
new file mode 100644
index 0000000000..9a03d92e48
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -0,0 +1,343 @@
+/*
+ * 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.uioverrides;
+
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
+import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
+import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
+import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Stream;
+
+public class QuickstepLauncher extends BaseQuickstepLauncher {
+
+ public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
+ /**
+ * Reusable command for applying the shelf height on the background thread.
+ */
+ public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
+ SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.createPredictor();
+ }
+ }
+
+ @Override
+ protected void setupViews() {
+ super.setupViews();
+ if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+ mHotseatPredictionController = new HotseatPredictionController(this);
+ }
+ }
+
+ @Override
+ protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+ StatsLogger logger = getStatsLogManager()
+ .logger().withItemInfo(info).withInstanceId(instanceId);
+ OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info);
+ allAppsRank.ifPresent(logger::withRank);
+ logger.log(LAUNCHER_APP_LAUNCH_TAP);
+
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ onStateOrResumeChanging(false /* inTransition */);
+ }
+
+ @Override
+ public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
+ @Nullable String sourceContainer) {
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.setPauseUIUpdate(true);
+ }
+ return super.startActivitySafely(v, intent, item, sourceContainer);
+ }
+
+ @Override
+ protected void onActivityFlagsChanged(int changeBits) {
+ super.onActivityFlagsChanged(changeBits);
+ if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
+ | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
+ onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
+ }
+
+ if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0
+ || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
+ mHotseatPredictionController.setPauseUIUpdate(false);
+ }
+ }
+
+ @Override
+ public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
+ super.folderCreatedFromItem(folder, itemInfo);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
+ }
+ }
+
+ @Override
+ public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
+ super.folderConvertedToItem(folder, itemInfo);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
+ }
+ }
+
+ @Override
+ public Stream getSupportedShortcuts() {
+ if (mHotseatPredictionController != null) {
+ return Stream.concat(super.getSupportedShortcuts(),
+ Stream.of(mHotseatPredictionController));
+ } else {
+ return super.getSupportedShortcuts();
+ }
+ }
+
+ /**
+ * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
+ */
+ private void onStateOrResumeChanging(boolean inTransition) {
+ LauncherState state = getStateManager().getState();
+ DeviceProfile profile = getDeviceProfile();
+ boolean willUserBeActive = (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
+ boolean visible = (state == NORMAL || state == OVERVIEW)
+ && (willUserBeActive || isUserActive())
+ && !profile.isVerticalBarLayout()
+ && profile.isPhone && !profile.isLandscape;
+ UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
+ profile.hotseatBarSizePx);
+ if (state == NORMAL && !inTransition) {
+ ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
+ }
+ }
+
+ @Override
+ public void bindPredictedItems(List appInfos, IntArray ranks) {
+ super.bindPredictedItems(appInfos, ranks);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.showCachedItems(appInfos, ranks);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.destroy();
+ mHotseatPredictionController = null;
+ }
+ }
+
+ @Override
+ public void onStateSetEnd(LauncherState state) {
+ super.onStateSetEnd(state);
+
+ switch (state.ordinal) {
+ case HINT_STATE_ORDINAL: {
+ Workspace workspace = getWorkspace();
+ boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE;
+ getStateManager().goToState(NORMAL, true,
+ willMoveScreens ? null : getScrimView()::startDragHandleEducationAnim);
+ if (willMoveScreens) {
+ workspace.post(workspace::moveToDefaultScreen);
+ }
+ break;
+ }
+ case OVERVIEW_STATE_ORDINAL: {
+ RecentsView recentsView = getOverviewPanel();
+ DiscoveryBounce.showForOverviewIfNeeded(this,
+ recentsView.getPagedOrientationHandler());
+ RecentsView rv = getOverviewPanel();
+ sendCustomAccessibilityEvent(
+ rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null);
+ break;
+ }
+ case QUICK_SWITCH_STATE_ORDINAL: {
+ RecentsView rv = getOverviewPanel();
+ TaskView tasktolaunch = rv.getTaskViewAt(0);
+ if (tasktolaunch != null) {
+ tasktolaunch.launchTask(false, success -> {
+ if (!success) {
+ getStateManager().goToState(OVERVIEW);
+ tasktolaunch.notifyTaskLaunchFailed(TAG);
+ } else {
+ getStateManager().moveToRestState();
+ }
+ }, MAIN_EXECUTOR.getHandler());
+ } else {
+ getStateManager().goToState(NORMAL);
+ }
+ break;
+ }
+
+ }
+ }
+
+ @Override
+ public TouchController[] createTouchControllers() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.1");
+ }
+ Mode mode = SysUINavigationMode.getMode(this);
+
+ ArrayList list = new ArrayList<>();
+ list.add(getDragController());
+ if (mode == NO_BUTTON) {
+ list.add(new NoButtonQuickSwitchTouchController(this));
+ list.add(new NavBarToHomeTouchController(this));
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.2");
+ }
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.3");
+ }
+ list.add(new NoButtonNavbarToOverviewTouchController(this));
+ } else {
+ list.add(new FlingAndHoldTouchController(this));
+ }
+ } else {
+ if (getDeviceProfile().isVerticalBarLayout()) {
+ list.add(new OverviewToAllAppsTouchController(this));
+ list.add(new LandscapeEdgeSwipeController(this));
+ if (mode.hasGestures) {
+ list.add(new TransposedQuickSwitchTouchController(this));
+ }
+ } else {
+ list.add(new PortraitStatesTouchController(this,
+ mode.hasGestures /* allowDragToOverview */));
+ if (mode.hasGestures) {
+ list.add(new QuickSwitchTouchController(this));
+ }
+ }
+ }
+
+ if (!getDeviceProfile().isMultiWindowMode) {
+ list.add(new StatusBarTouchController(this));
+ }
+
+ list.add(new LauncherTaskViewController(this));
+ return list.toArray(new TouchController[list.size()]);
+ }
+
+ @Override
+ public AtomicAnimationFactory createAtomicAnimationFactory() {
+ return new QuickstepAtomicAnimationFactory(this);
+ }
+
+ private static final class LauncherTaskViewController extends
+ TaskViewTouchController {
+
+ LauncherTaskViewController(Launcher activity) {
+ super(activity);
+ }
+
+ @Override
+ protected boolean isRecentsInteractive() {
+ return mActivity.isInState(OVERVIEW) || mActivity.isInState(OVERVIEW_MODAL_TASK);
+ }
+
+ @Override
+ protected boolean isRecentsModal() {
+ return mActivity.isInState(OVERVIEW_MODAL_TASK);
+ }
+
+ @Override
+ protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+ mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
+ }
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ RecentsView recentsView = getOverviewPanel();
+ writer.println("\nQuickstepLauncher:");
+ writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
+ recentsView.getPagedViewOrientedState()));
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
deleted file mode 100644
index cac170c68c..0000000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * 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.uioverrides;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.Gravity;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.RotationMode;
-import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
-import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
-import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.TouchInteractionService;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import java.util.ArrayList;
-
-/**
- * Provides recents-related {@link UiFactory} logic and classes.
- */
-public abstract class RecentsUiFactory {
-
- public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
- private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
- WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
-
- public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
- @Override
- public void mapRect(int left, int top, int right, int bottom, Rect out) {
- out.left = top;
- out.top = right;
- out.right = bottom;
- out.bottom = left;
- }
-
- @Override
- public void mapInsets(Context context, Rect insets, Rect out) {
- // If there is a display cutout, the top insets in portrait would also include the
- // cutout, which we will get as the left inset in landscape. Using the max of left and
- // top allows us to cover both cases (with or without cutout).
- if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
- out.top = Math.max(insets.top, insets.left);
- out.bottom = Math.max(insets.right, insets.bottom);
- out.left = out.right = 0;
- } else {
- out.top = Math.max(insets.top, insets.left);
- out.bottom = insets.right;
- out.left = insets.bottom;
- out.right = 0;
- }
- }
- };
-
- public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) {
- @Override
- public void mapRect(int left, int top, int right, int bottom, Rect out) {
- out.left = bottom;
- out.top = left;
- out.right = top;
- out.bottom = right;
- }
-
- @Override
- public void mapInsets(Context context, Rect insets, Rect out) {
- if (SysUINavigationMode.getMode(context) == NO_BUTTON) {
- out.top = Math.max(insets.top, insets.right);
- out.bottom = Math.max(insets.left, insets.bottom);
- out.left = out.right = 0;
- } else {
- out.top = Math.max(insets.top, insets.right);
- out.bottom = insets.left;
- out.right = insets.bottom;
- out.left = 0;
- }
- }
-
- @Override
- public int toNaturalGravity(int absoluteGravity) {
- int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
-
- if (horizontalGravity == Gravity.RIGHT) {
- horizontalGravity = Gravity.LEFT;
- } else if (horizontalGravity == Gravity.LEFT) {
- horizontalGravity = Gravity.RIGHT;
- }
-
- if (verticalGravity == Gravity.TOP) {
- verticalGravity = Gravity.BOTTOM;
- } else if (verticalGravity == Gravity.BOTTOM) {
- verticalGravity = Gravity.TOP;
- }
-
- return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK)
- & ~Gravity.VERTICAL_GRAVITY_MASK)
- | horizontalGravity | verticalGravity;
- }
- };
-
- public static RotationMode getRotationMode(DeviceProfile dp) {
- return !dp.isVerticalBarLayout() ? RotationMode.NORMAL
- : (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE);
- }
-
- public static TouchController[] createTouchControllers(Launcher launcher) {
- Mode mode = SysUINavigationMode.getMode(launcher);
-
- ArrayList list = new ArrayList<>();
- list.add(launcher.getDragController());
- if (mode == NO_BUTTON) {
- list.add(new NoButtonQuickSwitchTouchController(launcher));
- list.add(new NavBarToHomeTouchController(launcher));
- list.add(new FlingAndHoldTouchController(launcher));
- } else {
- if (launcher.getDeviceProfile().isVerticalBarLayout()) {
- list.add(new OverviewToAllAppsTouchController(launcher));
- list.add(new LandscapeEdgeSwipeController(launcher));
- if (mode.hasGestures) {
- list.add(new TransposedQuickSwitchTouchController(launcher));
- }
- } else {
- list.add(new PortraitStatesTouchController(launcher,
- mode.hasGestures /* allowDragToOverview */));
- if (mode.hasGestures) {
- list.add(new QuickSwitchTouchController(launcher));
- }
- }
- }
-
- if (FeatureFlags.PULL_DOWN_STATUS_BAR
- && !launcher.getDeviceProfile().isMultiWindowMode) {
- list.add(new StatusBarTouchController(launcher));
- }
-
- list.add(new LauncherTaskViewController(launcher));
- return list.toArray(new TouchController[list.size()]);
- }
-
- /**
- * Creates and returns the controller responsible for recents view state transitions.
- *
- * @param launcher the launcher activity
- * @return state handler for recents
- */
- public static StateHandler createRecentsViewStateController(Launcher launcher) {
- return new RecentsViewStateController(launcher);
- }
-
- /**
- * Clears the swipe shared state for the current swipe gesture.
- */
- public static void clearSwipeSharedState(boolean finishAnimation) {
- TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
- }
-
- /**
- * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
- *
- * @param launcher the launcher activity
- */
- public static void onLauncherStateOrResumeChanged(Launcher launcher) {
- LauncherState state = launcher.getStateManager().getState();
- DeviceProfile profile = launcher.getDeviceProfile();
- boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
- && !profile.isVerticalBarLayout();
- UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
- visible ? 1 : 0, profile.hotseatBarSizePx);
-
- if (state == NORMAL) {
- launcher.getOverviewPanel().setSwipeDownShouldLaunchApp(false);
- }
- }
-
- private static final class LauncherTaskViewController extends
- TaskViewTouchController {
-
- LauncherTaskViewController(Launcher activity) {
- super(activity);
- }
-
- @Override
- protected boolean isRecentsInteractive() {
- return mActivity.isInState(OVERVIEW);
- }
-
- @Override
- protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
- mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
- }
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index b5d84247a8..085b9b3af9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,23 +15,25 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON;
+import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
-import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.FloatProperty;
import androidx.annotation.NonNull;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
@@ -44,7 +46,7 @@ import com.android.quickstep.views.RecentsView;
public final class RecentsViewStateController extends
BaseRecentsViewStateController {
- public RecentsViewStateController(Launcher launcher) {
+ public RecentsViewStateController(BaseQuickstepLauncher launcher) {
super(launcher);
}
@@ -55,40 +57,40 @@ public final class RecentsViewStateController extends
mRecentsView.updateEmptyMessage();
mRecentsView.resetTaskVisuals();
}
- setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state.getVisibleElements(mLauncher));
+ setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state);
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
}
@Override
- void setStateWithAnimationInternal(@NonNull final LauncherState toState,
- @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
- super.setStateWithAnimationInternal(toState, builder, config);
-
- if (!toState.overviewUi) {
- builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
- }
+ void setStateWithAnimationInternal(@NonNull LauncherState toState,
+ @NonNull StateAnimationConfig config, @NonNull PendingAnimation builder) {
+ super.setStateWithAnimationInternal(toState, config, builder);
if (toState.overviewUi) {
- ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
- updateAnim.addUpdateListener(valueAnimator -> {
- // While animating into recents, update the visible task data as needed
- mRecentsView.loadVisibleTaskData();
- });
- updateAnim.setDuration(config.duration);
- builder.play(updateAnim);
+ // While animating into recents, update the visible task data as needed
+ builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
mRecentsView.updateEmptyMessage();
+ } else {
+ builder.addListener(
+ AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
}
- PropertySetter propertySetter = config.getPropertySetter(builder);
- setAlphas(propertySetter, toState.getVisibleElements(mLauncher));
- float fullscreenProgress = toState.getOverviewFullscreenProgress();
- propertySetter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, fullscreenProgress, LINEAR);
+ setAlphas(builder, toState);
+ builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
+ toState.getOverviewFullscreenProgress(), LINEAR);
}
- private void setAlphas(PropertySetter propertySetter, int visibleElements) {
- boolean hasClearAllButton = (visibleElements & RECENTS_CLEAR_ALL_BUTTON) != 0;
+ private void setAlphas(PropertySetter propertySetter, LauncherState state) {
+ float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
- hasClearAllButton ? 1f : 0f, LINEAR);
+ buttonAlpha, LINEAR);
+ propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
+ MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+ }
+
+ @Override
+ FloatProperty getTaskModalnessProperty() {
+ return TASK_MODALNESS;
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 63ac528047..8ff05f2aaa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -15,36 +15,29 @@
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import android.content.Context;
-import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
/**
* State indicating that the Launcher is behind an app
*/
public class BackgroundAppState extends OverviewState {
- private static final int STATE_FLAGS =
- FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY
- | FLAG_DISABLE_INTERACTION;
+ private static final int STATE_FLAGS = FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI
+ | FLAG_WORKSPACE_INACCESSIBLE | FLAG_NON_INTERACTIVE | FLAG_CLOSE_POPUPS;
public BackgroundAppState(int id) {
this(id, LauncherLogProto.ContainerType.TASKSWITCHER);
}
protected BackgroundAppState(int id, int logContainer) {
- super(id, logContainer, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
- }
-
- @Override
- public void onStateEnabled(Launcher launcher) {
- AbstractFloatingView.closeAllOpenViews(launcher, false);
+ super(id, logContainer, STATE_FLAGS);
}
@Override
@@ -52,8 +45,10 @@ public class BackgroundAppState extends OverviewState {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return super.getVerticalProgress(launcher);
}
+ RecentsView recentsView = launcher.getOverviewPanel();
int transitionLength = LayoutUtils.getShelfTrackingDistance(launcher,
- launcher.getDeviceProfile());
+ launcher.getDeviceProfile(),
+ recentsView.getPagedOrientationHandler());
AllAppsTransitionController controller = launcher.getAllAppsController();
float scrollRange = Math.max(controller.getShiftRange(), 1);
float progressDelta = (transitionLength / scrollRange);
@@ -61,25 +56,8 @@ public class BackgroundAppState extends OverviewState {
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- // Initialize the recents view scale to what it would be when starting swipe up
- RecentsView recentsView = launcher.getOverviewPanel();
- int taskCount = recentsView.getTaskViewCount();
- if (taskCount == 0) {
- return super.getOverviewScaleAndTranslation(launcher);
- }
- TaskView dummyTask;
- if (recentsView.getCurrentPage() >= 0) {
- if (recentsView.getCurrentPage() <= taskCount - 1) {
- dummyTask = recentsView.getCurrentPageTaskView();
- } else {
- dummyTask = recentsView.getTaskViewAt(taskCount - 1);
- }
- } else {
- dummyTask = recentsView.getTaskViewAt(0);
- }
- return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask)
- .getScaleAndTranslation();
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return getOverviewScaleAndOffsetForBackgroundState(launcher);
}
@Override
@@ -90,18 +68,32 @@ public class BackgroundAppState extends OverviewState {
@Override
public int getVisibleElements(Launcher launcher) {
return super.getVisibleElements(launcher)
- & ~RECENTS_CLEAR_ALL_BUTTON & ~VERTICAL_SWIPE_INDICATOR;
+ & ~OVERVIEW_BUTTONS & ~VERTICAL_SWIPE_INDICATOR;
}
@Override
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
// Translate hotseat offscreen if we show it in overview.
+ RecentsView recentsView = launcher.getOverviewPanel();
ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
- launcher.getDeviceProfile());
+ launcher.getDeviceProfile(),
+ recentsView.getPagedOrientationHandler());
return scaleAndTranslation;
}
return super.getHotseatScaleAndTranslation(launcher);
}
+
+ @Override
+ protected float getDepthUnchecked(Context context) {
+ return 1f;
+ }
+
+ public static float[] getOverviewScaleAndOffsetForBackgroundState(
+ BaseDraggingActivity activity) {
+ return new float[] {
+ ((RecentsView) activity.getOverviewPanel()).getMaxScaleForFullScreen(),
+ NO_OFFSET};
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
new file mode 100644
index 0000000000..fc0dcd5119
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 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.uioverrides.states;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * An Overview state that shows the current task in a modal fashion. Modal state is where the
+ * current task is shown on its own without other tasks visible.
+ */
+public class OverviewModalTaskState extends OverviewState {
+
+ private static final int STATE_FLAGS =
+ FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_WORKSPACE_INACCESSIBLE;
+
+ public OverviewModalTaskState(int id) {
+ super(id, ContainerType.OVERVIEW, STATE_FLAGS);
+ }
+
+ @Override
+ public int getTransitionDuration(Context launcher) {
+ return 300;
+ }
+
+ @Override
+ public int getVisibleElements(Launcher launcher) {
+ return OVERVIEW_BUTTONS;
+ }
+
+ @Override
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return getOverviewScaleAndOffsetForModalState(launcher);
+ }
+
+ @Override
+ public float getOverviewModalness() {
+ return 1.0f;
+ }
+
+ @Override
+ public void onBackPressed(Launcher launcher) {
+ launcher.getStateManager().goToState(LauncherState.OVERVIEW);
+ RecentsView recentsView = launcher.getOverviewPanel();
+ if (recentsView != null) {
+ recentsView.resetModalVisuals();
+ } else {
+ super.onBackPressed(launcher);
+ }
+ }
+
+ public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
+ Rect out = new Rect();
+ activity.getOverviewPanel().getTaskSize(out);
+ int taskHeight = out.height();
+ activity.getOverviewPanel().getModalTaskSize(out);
+ int newHeight = out.height();
+
+ float scale = (float) newHeight / taskHeight;
+
+ return new float[] {scale, NO_OFFSET};
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index 427206a65b..fc9a11bf66 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -14,38 +14,18 @@
* limitations under the License.
*/
package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimatorSetBuilder;
public class OverviewPeekState extends OverviewState {
+ private static final float OVERVIEW_OFFSET = 0.7f;
+
public OverviewPeekState(int id) {
super(id);
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- ScaleAndTranslation result = super.getOverviewScaleAndTranslation(launcher);
- result.translationX = NORMAL.getOverviewScaleAndTranslation(launcher).translationX
- - launcher.getResources().getDimension(R.dimen.overview_peek_distance);
- return result;
- }
-
- @Override
- public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
- AnimatorSetBuilder builder) {
- if (this == OVERVIEW_PEEK && fromState == NORMAL) {
- builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
- builder.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
- }
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {NO_SCALE, OVERVIEW_OFFSET};
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index ed5dba1fd5..d174bfd2d9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,35 +15,22 @@
*/
package com.android.launcher3.uioverrides.states;
-import static android.view.View.VISIBLE;
-
-import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import android.content.Context;
import android.graphics.Rect;
import android.view.View;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
-import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -56,24 +43,29 @@ import com.android.quickstep.views.TaskView;
*/
public class OverviewState extends LauncherState {
- // Scale recents takes before animating in
- private static final float RECENTS_PREPARE_SCALE = 1.33f;
-
protected static final Rect sTempRect = new Rect();
private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
- | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
+ | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_WORKSPACE_INACCESSIBLE
+ | FLAG_CLOSE_POPUPS;
public OverviewState(int id) {
- this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
+ this(id, STATE_FLAGS);
}
- protected OverviewState(int id, int transitionDuration, int stateFlags) {
- this(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
+ protected OverviewState(int id, int stateFlags) {
+ this(id, ContainerType.TASKSWITCHER, stateFlags);
}
- protected OverviewState(int id, int logContainer, int transitionDuration, int stateFlags) {
- super(id, logContainer, transitionDuration, stateFlags);
+ protected OverviewState(int id, int logContainer, int stateFlags) {
+ super(id, logContainer, stateFlags);
+ }
+
+ @Override
+ public int getTransitionDuration(Context context) {
+ // In no-button mode, overview comes in all the way from the left, so give it more time.
+ boolean isNoButtonMode = SysUINavigationMode.INSTANCE.get(context).getMode() == NO_BUTTON;
+ return isNoButtonMode && ENABLE_OVERVIEW_ACTIONS.get() ? 380 : 250;
}
@Override
@@ -110,19 +102,18 @@ public class OverviewState extends LauncherState {
}
@Override
- public ScaleAndTranslation getOverviewScaleAndTranslation(Launcher launcher) {
- return new ScaleAndTranslation(1f, 0f, 0f);
+ public float[] getOverviewScaleAndOffset(Launcher launcher) {
+ return new float[] {NO_SCALE, NO_OFFSET};
}
@Override
- public void onStateEnabled(Launcher launcher) {
- AbstractFloatingView.closeAllOpenViews(launcher);
- }
-
- @Override
- public void onStateTransitionEnd(Launcher launcher) {
- launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
- DiscoveryBounce.showForOverviewIfNeeded(launcher);
+ public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
+ if (this == OVERVIEW && ENABLE_OVERVIEW_ACTIONS.get()
+ && removeShelfFromOverview(launcher)) {
+ // Treat the QSB as part of the hotseat so they move together.
+ return getHotseatScaleAndTranslation(launcher);
+ }
+ return super.getQsbScaleAndTranslation(launcher);
}
@Override
@@ -137,13 +128,17 @@ public class OverviewState extends LauncherState {
@Override
public int getVisibleElements(Launcher launcher) {
- if (launcher.getDeviceProfile().isVerticalBarLayout()) {
- return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
+ RecentsView recentsView = launcher.getOverviewPanel();
+ if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher) ||
+ hideShelfInTwoButtonLandscape(launcher, recentsView.getPagedOrientationHandler())) {
+ return OVERVIEW_BUTTONS;
+ } else if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+ return VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS;
} else {
boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null
&& launcher.getAppsView().getFloatingHeaderView().hasVisibleContent();
- return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
- (hasAllAppsHeaderExtra ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
+ return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS
+ | (hasAllAppsHeaderExtra ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
}
}
@@ -168,13 +163,18 @@ public class OverviewState extends LauncherState {
@Override
public String getDescription(Launcher launcher) {
- return launcher.getString(R.string.accessibility_desc_recent_apps);
+ return launcher.getString(R.string.accessibility_recent_apps);
}
public static float getDefaultSwipeHeight(Launcher launcher) {
return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
+ @Override
+ protected float getDepthUnchecked(Context context) {
+ return 1f;
+ }
+
@Override
public void onBackPressed(Launcher launcher) {
TaskView taskView = launcher.getOverviewPanel().getRunningTaskView();
@@ -187,30 +187,6 @@ public class OverviewState extends LauncherState {
}
}
- @Override
- public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
- AnimatorSetBuilder builder) {
- if (fromState == NORMAL && this == OVERVIEW) {
- if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
- builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL);
- builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
- } else {
- builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
-
- // Scale up the recents, if it is not coming from the side
- RecentsView overview = launcher.getOverviewPanel();
- if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
- SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
- }
- }
- builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7);
- builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
- }
- }
-
public static OverviewState newBackgroundState(int id) {
return new BackgroundAppState(id);
}
@@ -222,4 +198,11 @@ public class OverviewState extends LauncherState {
public static OverviewState newSwitchState(int id) {
return new QuickSwitchState(id);
}
+
+ /**
+ * New Overview substate that represents the overview in modal mode (one task shown on its own)
+ */
+ public static OverviewState newModalTaskState(int id) {
+ return new OverviewModalTaskState(id);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 6c9f46fc47..2c7373e206 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -15,23 +15,16 @@
*/
package com.android.launcher3.uioverrides.states;
-import android.os.Handler;
-import android.os.Looper;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
/**
* State to indicate we are about to launch a recent task. Note that this state is only used when
- * quick switching from launcher; quick switching from an app uses WindowTransformSwipeHelper.
- * @see com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget#NEW_TASK
+ * quick switching from launcher; quick switching from an app uses LauncherSwipeHandler.
+ * @see com.android.quickstep.GestureState.GestureEndTarget#NEW_TASK
*/
public class QuickSwitchState extends BackgroundAppState {
- private static final String TAG = "QuickSwitchState";
-
public QuickSwitchState(int id) {
super(id, LauncherLogProto.ContainerType.APP);
}
@@ -48,21 +41,4 @@ public class QuickSwitchState extends BackgroundAppState {
public int getVisibleElements(Launcher launcher) {
return NONE;
}
-
- @Override
- public void onStateTransitionEnd(Launcher launcher) {
- TaskView tasktolaunch = launcher.getOverviewPanel().getTaskViewAt(0);
- if (tasktolaunch != null) {
- tasktolaunch.launchTask(false, success -> {
- if (!success) {
- launcher.getStateManager().goToState(OVERVIEW);
- tasktolaunch.notifyTaskLaunchFailed(TAG);
- } else {
- launcher.getStateManager().moveToRestState();
- }
- }, new Handler(Looper.getMainLooper()));
- } else {
- launcher.getStateManager().goToState(NORMAL);
- }
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
new file mode 100644
index 0000000000..a0af79743b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2020 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.uioverrides.states;
+
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
+import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.RecentsAtomicAnimationFactory;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Animation factory for quickstep specific transitions
+ */
+public class QuickstepAtomicAnimationFactory extends
+ RecentsAtomicAnimationFactory {
+
+ // Scale recents takes before animating in
+ private static final float RECENTS_PREPARE_SCALE = 1.33f;
+
+ public static final int INDEX_SHELF_ANIM = RecentsAtomicAnimationFactory.NEXT_INDEX + 0;
+ public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM =
+ RecentsAtomicAnimationFactory.NEXT_INDEX + 1;
+
+ private static final int MY_ANIM_COUNT = 2;
+ protected static final int NEXT_INDEX = RecentsAtomicAnimationFactory.NEXT_INDEX
+ + MY_ANIM_COUNT;
+
+ // Due to use of physics, duration may differ between devices so we need to calculate and
+ // cache the value.
+ private int mHintToNormalDuration = -1;
+
+ public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
+
+ public QuickstepAtomicAnimationFactory(QuickstepLauncher activity) {
+ super(activity, MY_ANIM_COUNT);
+ }
+
+ @Override
+ public Animator createStateElementAnimation(int index, float... values) {
+ switch (index) {
+ case INDEX_SHELF_ANIM: {
+ AllAppsTransitionController aatc = mActivity.getAllAppsController();
+ Animator springAnim = aatc.createSpringAnimation(values);
+
+ if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
+ // Translate hotseat with the shelf until reaching overview.
+ float overviewProgress = OVERVIEW.getVerticalProgress(mActivity);
+ ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mActivity);
+ float shiftRange = aatc.getShiftRange();
+ if (values.length == 1) {
+ values = new float[] {aatc.getProgress(), values[0]};
+ }
+ ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+ hotseatAnim.addUpdateListener(anim -> {
+ float progress = (Float) anim.getAnimatedValue();
+ if (progress >= overviewProgress || mActivity.isInState(BACKGROUND_APP)) {
+ float hotseatShift = (progress - overviewProgress) * shiftRange;
+ mActivity.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+ }
+ });
+ hotseatAnim.setInterpolator(LINEAR);
+ hotseatAnim.setDuration(springAnim.getDuration());
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(hotseatAnim);
+ anim.play(springAnim);
+ return anim;
+ }
+
+ return springAnim;
+ }
+ case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
+
+ config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
+ if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
+ config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
+ }
+
+ StateManager stateManager = mActivity.getStateManager();
+ return stateManager.createAtomicAnimation(
+ stateManager.getCurrentStableState(), OVERVIEW, config);
+ }
+ default:
+ return super.createStateElementAnimation(index, values);
+ }
+ }
+
+ @Override
+ public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
+ StateAnimationConfig config) {
+ if (toState == NORMAL && fromState == OVERVIEW) {
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
+ config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
+ config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
+ config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
+ config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+ Workspace workspace = mActivity.getWorkspace();
+
+ // Start from a higher workspace scale, but only if we're invisible so we don't jump.
+ boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
+ if (isWorkspaceVisible) {
+ CellLayout currentChild = (CellLayout) workspace.getChildAt(
+ workspace.getCurrentPage());
+ isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
+ && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
+ }
+ if (!isWorkspaceVisible) {
+ workspace.setScaleX(0.92f);
+ workspace.setScaleY(0.92f);
+ }
+ Hotseat hotseat = mActivity.getHotseat();
+ boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
+ if (!isHotseatVisible) {
+ hotseat.setScaleX(0.92f);
+ hotseat.setScaleY(0.92f);
+ if (ENABLE_OVERVIEW_ACTIONS.get()) {
+ AllAppsContainerView qsbContainer = mActivity.getAppsView();
+ View qsb = qsbContainer.getSearchView();
+ boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
+ if (!qsbVisible) {
+ qsbContainer.setScaleX(0.92f);
+ qsbContainer.setScaleY(0.92f);
+ }
+ }
+ }
+ } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) {
+ // Keep fully visible until the very end (when overview is offscreen) to make invisible.
+ config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
+ } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) {
+ config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+ config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
+ } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
+ if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
+ config.setInterpolator(ANIM_WORKSPACE_SCALE,
+ fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+ } else {
+ config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+
+ // Scale up the recents, if it is not coming from the side
+ RecentsView overview = mActivity.getOverviewPanel();
+ if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
+ SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+ }
+ }
+ config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
+ Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
+ && removeShelfFromOverview(mActivity)
+ ? OVERSHOOT_1_2
+ : OVERSHOOT_1_7;
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
+ config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
+ } else if (fromState == HINT_STATE && toState == NORMAL) {
+ config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
+ if (mHintToNormalDuration == -1) {
+ ValueAnimator va = getSpringScaleAnimator(mActivity, mActivity.getWorkspace(),
+ toState.getWorkspaceScaleAndTranslation(mActivity).scale);
+ mHintToNormalDuration = (int) va.getDuration();
+ }
+ config.duration = Math.max(config.duration, mHintToNormalDuration);
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 3231f378db..fac478e777 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -16,38 +16,43 @@
package com.android.launcher3.uioverrides.touchcontrollers;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppTransitionManagerImpl;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.views.RecentsView;
@@ -60,7 +65,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
private static final long PEEK_OUT_ANIM_DURATION = 100;
private static final float MAX_DISPLACEMENT_PERCENT = 0.75f;
- private final MotionPauseDetector mMotionPauseDetector;
+ protected final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
private final float mMotionPauseMaxDisplacement;
@@ -70,61 +75,75 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
super(l, false /* allowDragToOverview */);
mMotionPauseDetector = new MotionPauseDetector(l);
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
- mMotionPauseMaxDisplacement = getShiftRange() * MAX_DISPLACEMENT_PERCENT;
+ mMotionPauseMaxDisplacement = getMotionPauseMaxDisplacement();
+ }
+
+ protected float getMotionPauseMaxDisplacement() {
+ return getShiftRange() * MAX_DISPLACEMENT_PERCENT;
}
@Override
protected long getAtomicDuration() {
- return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
+ return QuickstepAtomicAnimationFactory.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
}
@Override
- public void onDragStart(boolean start) {
+ public void onDragStart(boolean start, float startDisplacement) {
mMotionPauseDetector.clear();
- super.onDragStart(start);
+ super.onDragStart(start, startDisplacement);
if (handlingOverviewAnim()) {
- mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
- RecentsView recentsView = mLauncher.getOverviewPanel();
- recentsView.setOverviewStateEnabled(isPaused);
- if (mPeekAnim != null) {
- mPeekAnim.cancel();
- }
- LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
- LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
- long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
- mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
- new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration);
- mPeekAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPeekAnim = null;
- }
- });
- mPeekAnim.start();
- VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-
- mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
- peekDuration, 0);
- });
+ mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
}
+
+ if (mAtomicAnim != null) {
+ mAtomicAnim.cancel();
+ }
+ }
+
+ protected void onMotionPauseChanged(boolean isPaused) {
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.setOverviewStateEnabled(isPaused);
+ if (mPeekAnim != null) {
+ mPeekAnim.cancel();
+ }
+ LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
+ LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
+ long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
+
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.duration = peekDuration;
+ config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
+ mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(
+ fromState, toState, config);
+ mPeekAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPeekAnim = null;
+ }
+ });
+ mPeekAnim.start();
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+
+ mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(isPaused ? 0 : 1)
+ .setDuration(peekDuration).start();
}
/**
* @return Whether we are handling the overview animation, rather than
* having it as part of the existing animation to the target state.
*/
- private boolean handlingOverviewAnim() {
- int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+ protected boolean handlingOverviewAnim() {
+ int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
}
@Override
- protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
- LauncherState toState) {
+ protected StateAnimationConfig getConfigForStates(
+ LauncherState fromState, LauncherState toState) {
if (fromState == NORMAL && toState == ALL_APPS) {
- AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ StateAnimationConfig builder = new StateAnimationConfig();
// Fade in prediction icons quickly, then rest of all apps after reaching overview.
float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
- OVERVIEW.getVerticalProgress(mLauncher);
@@ -143,7 +162,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
return builder;
} else if (fromState == ALL_APPS && toState == NORMAL) {
- AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ StateAnimationConfig builder = new StateAnimationConfig();
// Keep all apps/predictions opaque until the very end of the transition.
float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
@@ -156,34 +175,26 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
1));
return builder;
}
- return super.getAnimatorSetBuilderForStates(fromState, toState);
+ return super.getConfigForStates(fromState, toState);
}
@Override
public boolean onDrag(float displacement, MotionEvent event) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "FlingAndHoldTouchController");
+ }
float upDisplacement = -displacement;
- mMotionPauseDetector.setDisallowPause(upDisplacement < mMotionPauseMinDisplacement
+ mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
+ || upDisplacement < mMotionPauseMinDisplacement
|| upDisplacement > mMotionPauseMaxDisplacement);
- mMotionPauseDetector.addPosition(displacement, event.getEventTime());
+ mMotionPauseDetector.addPosition(event);
return super.onDrag(displacement, event);
}
@Override
public void onDragEnd(float velocity) {
if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
- if (mPeekAnim != null) {
- mPeekAnim.cancel();
- }
-
- Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
- INDEX_PAUSE_TO_OVERVIEW_ANIM);
- overviewAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
- }
- });
- overviewAnim.start();
+ goToOverviewOnDragEnd(velocity);
} else {
super.onDragEnd(velocity);
}
@@ -195,6 +206,38 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
mMotionPauseDetector.clear();
}
+ protected void goToOverviewOnDragEnd(float velocity) {
+ if (mPeekAnim != null) {
+ mPeekAnim.cancel();
+ }
+
+ Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
+ .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
+ mAtomicAnim = new AnimatorSet();
+ mAtomicAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mCancelled) {
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
+ config.duration = PEEK_OUT_ANIM_DURATION;
+ mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(
+ mFromState, mToState, config);
+ mPeekAnim.start();
+ }
+ mAtomicAnim = null;
+ }
+ });
+ mAtomicAnim.play(overviewAnim);
+ mAtomicAnim.start();
+ }
+
@Override
protected void goToTargetState(LauncherState targetState, int logAction) {
if (mPeekAnim != null && mPeekAnim.isStarted()) {
@@ -211,11 +254,14 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
}
@Override
- protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+ @AnimationFlags
+ protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
if (handlingOverviewAnim()) {
// We don't want the state transition to all apps to animate overview,
// as that will cause a jump after our atomic animation.
- builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+ return animComponents | SKIP_OVERVIEW;
+ } else {
+ return animComponents;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index ef50c7b5d4..c1a585ea9d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -15,36 +15,39 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
-import static android.view.View.TRANSLATION_X;
-
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.BaseFlags;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -98,18 +101,37 @@ public class NavBarToHomeTouchController implements TouchController,
}
private boolean canInterceptTouch(MotionEvent ev) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NavBarToHomeTouchController.canInterceptTouch "
+ + ev);
+ }
boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
if (!cameFromNavBar) {
return false;
}
- if (mStartState == OVERVIEW || mStartState == ALL_APPS) {
+ if (mStartState.overviewUi || mStartState == ALL_APPS) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED,
+ "NavBarToHomeTouchController.canInterceptTouch true 1 "
+ + mStartState.overviewUi + " " + (mStartState == ALL_APPS));
+ }
return true;
}
- if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+ int typeToClose = ENABLE_ALL_APPS_EDU.get() ? TYPE_ALL & ~TYPE_ALL_APPS_EDU : TYPE_ALL;
+ if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED,
+ "NavBarToHomeTouchController.canInterceptTouch true 2 "
+ + AbstractFloatingView.getTopOpenView(mLauncher), new Exception());
+ }
return true;
}
- if (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+ if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
&& AssistantUtilities.isExcludedAssistantRunning()) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED,
+ "NavBarToHomeTouchController.canInterceptTouch true 3");
+ }
return true;
}
return false;
@@ -125,47 +147,40 @@ public class NavBarToHomeTouchController implements TouchController,
}
@Override
- public void onDragStart(boolean start) {
+ public void onDragStart(boolean start, float startDisplacement) {
initCurrentAnimation();
}
private void initCurrentAnimation() {
long accuracy = (long) (getShiftRange() * 2);
- final AnimatorSet anim = new AnimatorSet();
- if (mStartState == OVERVIEW) {
+ final PendingAnimation builder = new PendingAnimation(accuracy);
+ if (mStartState.overviewUi) {
RecentsView recentsView = mLauncher.getOverviewPanel();
- float pullbackDist = mPullbackDistance;
- if (!recentsView.isRtl()) {
- pullbackDist = -pullbackDist;
+ builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
+ -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ builder.addOnFrameCallback(
+ () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
}
- Animator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, pullbackDist);
- pullback.setInterpolator(PULLBACK_INTERPOLATOR);
- anim.play(pullback);
} else if (mStartState == ALL_APPS) {
- AnimatorSetBuilder builder = new AnimatorSetBuilder();
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- Animator allAppsProgress = ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
- -mPullbackDistance / allAppsController.getShiftRange());
- allAppsProgress.setInterpolator(PULLBACK_INTERPOLATOR);
- builder.play(allAppsProgress);
+ builder.setFloat(allAppsController, ALL_APPS_PROGRESS,
+ -mPullbackDistance / allAppsController.getShiftRange(), PULLBACK_INTERPOLATOR);
+
// Slightly fade out all apps content to further distinguish from scrolling.
- builder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, Interpolators
- .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
- AnimationConfig config = new AnimationConfig();
+ StateAnimationConfig config = new StateAnimationConfig();
config.duration = accuracy;
- allAppsController.setAlphas(mEndState.getVisibleElements(mLauncher), config, builder);
- anim.play(builder.build());
+ config.setInterpolator(StateAnimationConfig.ANIM_ALL_APPS_FADE, Interpolators
+ .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
+
+ allAppsController.setAlphas(mEndState, config, builder);
}
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
if (topView != null) {
- Animator hintCloseAnim = topView.createHintCloseAnim(mPullbackDistance);
- if (hintCloseAnim != null) {
- hintCloseAnim.setInterpolator(PULLBACK_INTERPOLATOR);
- anim.play(hintCloseAnim);
- }
+ topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder);
}
- anim.setDuration(accuracy);
- mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy, this::clearState);
+ mCurrentAnimation = builder.createPlaybackController()
+ .setOnCancelRunnable(this::clearState);
}
private void clearState() {
@@ -192,6 +207,11 @@ public class NavBarToHomeTouchController implements TouchController,
boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
|| (velocity < 0 && fling);
if (success) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.switchToScreenshot(null,
+ () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
+ }
mLauncher.getStateManager().goToState(mEndState, true,
() -> onSwipeInteractionCompleted(mEndState));
if (mStartState != mEndState) {
@@ -203,17 +223,13 @@ public class NavBarToHomeTouchController implements TouchController,
logStateChange(topOpenView.getLogContainerType(), logAction);
}
ActivityManagerWrapper.getInstance()
- .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
} else {
// Quickly return to the state we came from (we didn't move far).
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
anim.setFloatValues(progress, 0);
- anim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- onSwipeInteractionCompleted(mStartState);
- }
- });
+ anim.addListener(AnimationSuccessListener.forRunnable(
+ () -> onSwipeInteractionCompleted(mStartState)));
anim.setDuration(80).start();
}
}
@@ -232,5 +248,9 @@ public class NavBarToHomeTouchController implements TouchController,
startContainerType,
mEndState.containerType,
mLauncher.getWorkspace().getCurrentPage());
+ mLauncher.getStatsLogManager().logger()
+ .withSrcState(StatsLogManager.containerTypeToAtomState(mStartState.containerType))
+ .withDstState(StatsLogManager.containerTypeToAtomState(mEndState.containerType))
+ .log(LAUNCHER_HOME_GESTURE);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
new file mode 100644
index 0000000000..9316938c45
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 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.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
+ * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
+ * first home screen instead of to Overview.
+ */
+public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchController {
+
+
+ // How much of the movement to use for translating overview after swipe and hold.
+ private static final float OVERVIEW_MOVEMENT_FACTOR = 0.25f;
+ private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
+ private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
+
+ private final RecentsView mRecentsView;
+
+ private boolean mDidTouchStartInNavBar;
+ private boolean mReachedOverview;
+ private boolean mIsOverviewRehidden;
+ private boolean mIsHomeStaggeredAnimFinished;
+ // The last recorded displacement before we reached overview.
+ private PointF mStartDisplacement = new PointF();
+
+ // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
+ private ObjectAnimator mNormalToHintOverviewScrimAnimator;
+
+ public NoButtonNavbarToOverviewTouchController(Launcher l) {
+ super(l);
+ mRecentsView = l.getOverviewPanel();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController.ctor");
+ }
+ }
+
+ @Override
+ protected float getMotionPauseMaxDisplacement() {
+ // No need to disallow pause when swiping up all the way up the screen (unlike
+ // FlingAndHoldTouchController where user is probably intending to go to all apps).
+ return Float.MAX_VALUE;
+ }
+
+ @Override
+ protected boolean canInterceptTouch(MotionEvent ev) {
+ mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+ return super.canInterceptTouch(ev);
+ }
+
+ @Override
+ protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+ if (fromState == NORMAL && mDidTouchStartInNavBar) {
+ return HINT_STATE;
+ } else if (fromState == OVERVIEW && isDragTowardPositive) {
+ // Don't allow swiping up to all apps.
+ return OVERVIEW;
+ }
+ return super.getTargetState(fromState, isDragTowardPositive);
+ }
+
+ @Override
+ protected float initCurrentAnimation(int animComponents) {
+ float progressMultiplier = super.initCurrentAnimation(animComponents);
+ if (mToState == HINT_STATE) {
+ // Track the drag across the entire height of the screen.
+ progressMultiplier = -1 / getShiftRange();
+ }
+ return progressMultiplier;
+ }
+
+ @Override
+ public void onDragStart(boolean start, float startDisplacement) {
+ super.onDragStart(start, startDisplacement);
+ if (mFromState == NORMAL && mToState == HINT_STATE) {
+ mNormalToHintOverviewScrimAnimator = ObjectAnimator.ofFloat(
+ mLauncher.getDragLayer().getOverviewScrim(),
+ OverviewScrim.SCRIM_PROGRESS,
+ mFromState.getOverviewScrimAlpha(mLauncher),
+ mToState.getOverviewScrimAlpha(mLauncher));
+ }
+ mReachedOverview = false;
+ }
+
+ @Override
+ protected void updateProgress(float fraction) {
+ super.updateProgress(fraction);
+ if (mNormalToHintOverviewScrimAnimator != null) {
+ mNormalToHintOverviewScrimAnimator.setCurrentFraction(fraction);
+ }
+ }
+
+ @Override
+ public void onDragEnd(float velocity) {
+ super.onDragEnd(velocity);
+ mNormalToHintOverviewScrimAnimator = null;
+ }
+
+ @Override
+ protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+ LauncherState targetState, float velocity, boolean isFling) {
+ super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
+ isFling);
+ if (targetState == HINT_STATE) {
+ // Normally we compute the duration based on the velocity and distance to the given
+ // state, but since the hint state tracks the entire screen without a clear endpoint, we
+ // need to manually set the duration to a reasonable value.
+ animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher));
+ }
+ }
+
+ @Override
+ protected void onMotionPauseChanged(boolean isPaused) {
+ if (mCurrentAnimation == null) {
+ return;
+ }
+ mNormalToHintOverviewScrimAnimator = null;
+ mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
+ mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
+ mReachedOverview = true;
+ maybeSwipeInteractionToOverviewComplete();
+ });
+ });
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+ }
+
+ private void maybeSwipeInteractionToOverviewComplete() {
+ if (mReachedOverview && mDetector.isSettlingState()) {
+ onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+ }
+ }
+
+ // Used if flinging back to home after reaching overview
+ private void maybeSwipeInteractionToHomeComplete() {
+ if (mIsHomeStaggeredAnimFinished && mIsOverviewRehidden) {
+ onSwipeInteractionCompleted(NORMAL, Touch.FLING);
+ }
+ }
+
+ @Override
+ protected boolean handlingOverviewAnim() {
+ return mDidTouchStartInNavBar && super.handlingOverviewAnim();
+ }
+
+ @Override
+ public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController");
+ }
+ if (mMotionPauseDetector.isPaused()) {
+ if (!mReachedOverview) {
+ mStartDisplacement.set(xDisplacement, yDisplacement);
+ } else {
+ mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x)
+ * OVERVIEW_MOVEMENT_FACTOR);
+ mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
+ * OVERVIEW_MOVEMENT_FACTOR);
+ }
+ // Stay in Overview.
+ return true;
+ }
+ return super.onDrag(yDisplacement, xDisplacement, event);
+ }
+
+ @Override
+ protected void goToOverviewOnDragEnd(float velocity) {
+ float velocityDp = dpiFromPx(velocity);
+ boolean isFling = Math.abs(velocityDp) > 1;
+ StateManager stateManager = mLauncher.getStateManager();
+ boolean goToHomeInsteadOfOverview = isFling;
+ if (goToHomeInsteadOfOverview) {
+ if (velocity > 0) {
+ stateManager.goToState(NORMAL, true,
+ () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
+ } else {
+ mIsHomeStaggeredAnimFinished = mIsOverviewRehidden = false;
+
+ StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
+ mLauncher, velocity, false /* animateOverviewScrim */);
+ staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mIsHomeStaggeredAnimFinished = true;
+ maybeSwipeInteractionToHomeComplete();
+ }
+ }).start();
+
+ // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
+ stateManager.cancelAnimation();
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.duration = OVERVIEW.getTransitionDuration(mLauncher);
+ config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
+ AnimatorSet anim = stateManager.createAtomicAnimation(
+ stateManager.getState(), NORMAL, config);
+ anim.addListener(AnimationSuccessListener.forRunnable(() -> {
+ mIsOverviewRehidden = true;
+ maybeSwipeInteractionToHomeComplete();
+ }));
+ anim.start();
+ }
+ }
+ if (mReachedOverview) {
+ float distanceDp = dpiFromPx(Math.max(
+ Math.abs(mRecentsView.getTranslationX()),
+ Math.abs(mRecentsView.getTranslationY())));
+ long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
+ distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
+ mRecentsView.animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(ACCEL_DEACCEL)
+ .setDuration(duration)
+ .withEndAction(goToHomeInsteadOfOverview
+ ? null
+ : this::maybeSwipeInteractionToOverviewComplete);
+ }
+ }
+
+ private float dpiFromPx(float pixels) {
+ return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics());
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 76374af6a8..1b439d1d31 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -16,54 +16,57 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
+import static com.android.launcher3.logging.StatsLogManager.getLauncherAtomEvent;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
+import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.view.MotionEvent;
-import android.view.View;
import android.view.animation.Interpolator;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -71,7 +74,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ShelfPeekAnim;
@@ -92,17 +95,18 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
- private final Launcher mLauncher;
+ private final BaseQuickstepLauncher mLauncher;
private final BothAxesSwipeDetector mSwipeDetector;
+ private final ShelfPeekAnim mShelfPeekAnim;
private final float mXRange;
private final float mYRange;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
+ private final LauncherRecentsView mRecentsView;
private boolean mNoIntercept;
private LauncherState mStartState;
- private ShelfPeekAnim mShelfPeekAnim;
private boolean mIsHomeScreenVisible = true;
// As we drag, we control 3 animations: one to get non-overview components out of the way,
@@ -111,11 +115,14 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
private AnimatorPlaybackController mXOverviewAnim;
private AnimatorPlaybackController mYOverviewAnim;
- public NoButtonQuickSwitchTouchController(Launcher launcher) {
+ public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
+ mShelfPeekAnim = mLauncher.getShelfPeekAnim();
+ mRecentsView = mLauncher.getOverviewPanel();
mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
- mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile());
+ mYRange = LayoutUtils.getShelfTrackingDistance(
+ mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
mMotionPauseDetector = new MotionPauseDetector(mLauncher);
mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
R.dimen.motion_pause_detector_min_displacement_from_app);
@@ -154,7 +161,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
- int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+ int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return false;
}
@@ -165,9 +172,6 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
public void onDragStart(boolean start) {
mMotionPauseDetector.clear();
if (start) {
- mShelfPeekAnim = ((QuickstepAppTransitionManagerImpl) mLauncher
- .getAppTransitionManager()).getShelfPeekAnim();
-
mStartState = mLauncher.getStateManager().getState();
mMotionPauseDetector.setOnMotionPauseListener(this);
@@ -182,14 +186,18 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
@Override
public void onMotionPauseChanged(boolean isPaused) {
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ return;
+ }
+
ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
if (shelfState == PEEK) {
// Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
- AnimatorSetBuilder builder = new AnimatorSetBuilder();
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher),
- new AnimationConfig(), builder);
- builder.build().setDuration(0).start();
+ allAppsController.setAlphas(
+ NORMAL, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
// Hotseat was hidden, but we need it visible when peeking.
@@ -198,86 +206,70 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
}
mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
ShelfPeekAnim.DURATION);
- VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
}
private void setupAnimators() {
// Animate the non-overview components (e.g. workspace, shelf) out of the way.
- AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder();
+ StateAnimationConfig nonOverviewBuilder = new StateAnimationConfig();
nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
- updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL);
+ updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder);
mNonOverviewAnim.dispatchOnStart();
+ if (mRecentsView.getTaskViewCount() == 0) {
+ mRecentsView.setOnEmptyMessageUpdatedListener(isEmpty -> {
+ if (!isEmpty && mSwipeDetector.isDraggingState()) {
+ // We have loaded tasks, update the animators to start at the correct scale etc.
+ setupOverviewAnimators();
+ }
+ });
+ }
+
setupOverviewAnimators();
}
/** Create state animation to control non-overview components. */
- private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
- @LauncherStateManager.AnimationComponents int animComponents) {
- builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW);
- long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
- mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
- builder, accuracy, this::clearState, animComponents);
+ private void updateNonOverviewAnim(LauncherState toState, StateAnimationConfig config) {
+ config.duration = (long) (Math.max(mXRange, mYRange) * 2);
+ config.animFlags = config.animFlags | SKIP_OVERVIEW;
+ mNonOverviewAnim = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(toState, config)
+ .setOnCancelRunnable(this::clearState);
}
private void setupOverviewAnimators() {
final LauncherState fromState = QUICK_SWITCH;
final LauncherState toState = OVERVIEW;
- LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState
- .getOverviewScaleAndTranslation(mLauncher);
- LauncherState.ScaleAndTranslation toScaleAndTranslation = toState
- .getOverviewScaleAndTranslation(mLauncher);
- // Update RecentView's translationX to have it start offscreen.
- LauncherRecentsView recentsView = mLauncher.getOverviewPanel();
- float startScale = Utilities.mapRange(
- SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS),
- fromScaleAndTranslation.scale,
- toScaleAndTranslation.scale);
- fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale);
// Set RecentView's initial properties.
- recentsView.setScaleX(fromScaleAndTranslation.scale);
- recentsView.setScaleY(fromScaleAndTranslation.scale);
- recentsView.setTranslationX(fromScaleAndTranslation.translationX);
- recentsView.setTranslationY(fromScaleAndTranslation.translationY);
- recentsView.setContentAlpha(1);
+ SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
+ ADJACENT_PAGE_OFFSET.set(mRecentsView, 1f);
+ mRecentsView.setContentAlpha(1);
+ mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
+ mLauncher.getActionsView().getVisibilityAlpha().setValue(
+ (fromState.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1f : 0f);
+ float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
// As we drag right, animate the following properties:
// - RecentsView translationX
// - OverviewScrim
- AnimatorSet xOverviewAnim = new AnimatorSet();
- xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X,
- toScaleAndTranslation.translationX));
- xOverviewAnim.play(ObjectAnimator.ofFloat(
- mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
- toState.getOverviewScrimAlpha(mLauncher)));
- long xAccuracy = (long) (mXRange * 2);
- xOverviewAnim.setDuration(xAccuracy);
- mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy);
+ PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
+ xAnim.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1], LINEAR);
+ xAnim.setFloat(mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
+ toState.getOverviewScrimAlpha(mLauncher), LINEAR);
+ mXOverviewAnim = xAnim.createPlaybackController();
mXOverviewAnim.dispatchOnStart();
// As we drag up, animate the following properties:
- // - RecentsView translationY
// - RecentsView scale
// - RecentsView fullscreenProgress
- AnimatorSet yAnimation = new AnimatorSet();
- Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y,
- toScaleAndTranslation.translationY);
- Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
- toScaleAndTranslation.scale);
- Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS,
- fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress());
- scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
- fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
- yAnimation.play(translateYAnim);
- yAnimation.play(scaleAnim);
- yAnimation.play(fullscreenProgressAnim);
- long yAccuracy = (long) (mYRange * 2);
- yAnimation.setDuration(yAccuracy);
- mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy);
+ PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2));
+ yAnim.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0], SCALE_DOWN_INTERPOLATOR);
+ yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
+ toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR);
+ mYOverviewAnim = yAnim.createPlaybackController();
mYOverviewAnim.dispatchOnStart();
}
@@ -303,7 +295,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
// home screen elements will appear in the shelf on motion pause.
mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible
|| -displacement.y < mMotionPauseMinDisplacement);
- mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime());
+ mMotionPauseDetector.addPosition(ev);
if (mIsHomeScreenVisible) {
// Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
@@ -328,8 +320,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
if (mMotionPauseDetector.isPaused() && noFling) {
cancelAnimations();
- Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
- INDEX_PAUSE_TO_OVERVIEW_ANIM);
+ Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
+ .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
overviewAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -378,7 +370,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
xOverviewAnim.setFloatValues(startXProgress, endXProgress);
xOverviewAnim.setDuration(xDuration)
.setInterpolator(scrollInterpolatorForVelocity(velocity.x));
- mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x);
+ mXOverviewAnim.dispatchOnStart();
boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
@@ -399,14 +391,16 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
yOverviewAnim.setFloatValues(startYProgress, endYProgress);
yOverviewAnim.setDuration(yDuration);
- mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y);
+ mYOverviewAnim.dispatchOnStart();
ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
if (flingUpToNormal && !mIsHomeScreenVisible) {
// We are flinging to home while workspace is invisible, run the same staggered
// animation as from an app.
+ StateAnimationConfig config = new StateAnimationConfig();
// Update mNonOverviewAnim to do nothing so it doesn't interfere.
- updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */);
+ config.animFlags = 0;
+ updateNonOverviewAnim(targetState, config);
nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
new StaggeredWorkspaceAnim(mLauncher, velocity.y, false /* animateOverviewScrim */)
@@ -421,8 +415,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
float startProgress = mNonOverviewAnim.getProgressFraction();
float endProgress = canceled ? 0 : 1;
nonOverviewAnim.setFloatValues(startProgress, endProgress);
- mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress,
- horizontalFling ? velocity.x : velocity.y);
+ mNonOverviewAnim.dispatchOnStart();
}
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
@@ -441,6 +434,13 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
mStartState.containerType,
targetState.containerType,
mLauncher.getWorkspace().getCurrentPage());
+ mLauncher.getStatsLogManager().logger()
+ .withSrcState(LAUNCHER_STATE_HOME)
+ .withDstState(StatsLogManager.containerTypeToAtomState(targetState.containerType))
+ .log(getLauncherAtomEvent(mStartState.containerType, targetState.containerType,
+ targetState.ordinal > mStartState.ordinal
+ ? LAUNCHER_UNKNOWN_SWIPEUP
+ : LAUNCHER_UNKNOWN_SWIPEDOWN));
mLauncher.getStateManager().goToState(targetState, false, this::clearState);
}
@@ -469,5 +469,6 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
mYOverviewAnim = null;
mIsHomeScreenVisible = true;
mSwipeDetector.finishedScrolling();
+ mRecentsView.setOnEmptyMessageUpdatedListener(null);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 03862db1d2..845699a761 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -19,9 +19,10 @@ package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.isTouchOverHotseat;
import android.view.MotionEvent;
+import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
-import com.android.launcher3.util.PendingAnimation;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -74,12 +75,12 @@ public final class PortraitOverviewStateTouchHelper {
* @param duration how long the animation should be
* @return the animation
*/
- PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
+ PendingAnimation createSwipeDownToTaskAppAnimation(long duration, Interpolator interpolator) {
mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
TaskView taskView = mRecentsView.getCurrentPageTaskView();
if (taskView == null) {
throw new IllegalStateException("There is no task view to animate to.");
}
- return mRecentsView.createTaskLauncherAnimation(taskView, duration);
+ return mRecentsView.createTaskLaunchAnimation(taskView, duration, interpolator);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 14216ffdfe..c643858e5c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -17,17 +17,17 @@ package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -35,20 +35,17 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_O
import android.view.MotionEvent;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -58,7 +55,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
*/
public class QuickSwitchTouchController extends AbstractStateChangeTouchController {
- private @Nullable TaskView mTaskToLaunch;
+ protected final RecentsView mOverviewPanel;
public QuickSwitchTouchController(Launcher launcher) {
this(launcher, SingleAxisSwipeDetector.HORIZONTAL);
@@ -66,6 +63,7 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
protected QuickSwitchTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
super(l, dir);
+ mOverviewPanel = l.getOverviewPanel();
}
@Override
@@ -84,7 +82,7 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+ int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return NORMAL;
}
@@ -92,46 +90,44 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
}
@Override
- public void onDragStart(boolean start) {
- super.onDragStart(start);
+ public void onDragStart(boolean start, float startDisplacement) {
+ super.onDragStart(start, startDisplacement);
mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
- mTaskToLaunch = mLauncher.getOverviewPanel().getTaskViewAt(0);
ActivityManagerWrapper.getInstance()
- .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
}
@Override
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
- mTaskToLaunch = null;
}
@Override
protected float initCurrentAnimation(int animComponents) {
- AnimatorSetBuilder animatorSetBuilder = new AnimatorSetBuilder();
- setupInterpolators(animatorSetBuilder);
- long accuracy = (long) (getShiftRange() * 2);
- mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
- animatorSetBuilder, accuracy, this::clearState, LauncherStateManager.ANIM_ALL);
- mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
- updateFullscreenProgress((Float) valueAnimator.getAnimatedValue());
- });
+ StateAnimationConfig config = new StateAnimationConfig();
+ setupInterpolators(config);
+ config.duration = (long) (getShiftRange() * 2);
+ mCurrentAnimation = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(mToState, config)
+ .setOnCancelRunnable(this::clearState);
+ mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator ->
+ updateFullscreenProgress((Float) valueAnimator.getAnimatedValue()));
return 1 / getShiftRange();
}
- private void setupInterpolators(AnimatorSetBuilder animatorSetBuilder) {
- animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_2);
- animatorSetBuilder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_2);
+ private void setupInterpolators(StateAnimationConfig stateAnimationConfig) {
+ stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_2);
if (SysUINavigationMode.getMode(mLauncher) == Mode.NO_BUTTON) {
// Overview lives to the left of workspace, so translate down later than over
- animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL_2);
- animatorSetBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCEL_2);
- animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_SCALE, ACCEL_2);
- animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCEL_2);
- animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+ stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_SCALE, ACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCEL_2);
+ stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
} else {
- animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, LINEAR);
- animatorSetBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, LINEAR);
+ stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, LINEAR);
+ stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, LINEAR);
}
}
@@ -142,13 +138,15 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
}
private void updateFullscreenProgress(float progress) {
- if (mTaskToLaunch != null) {
- mTaskToLaunch.setFullscreenProgress(progress);
- int sysuiFlags = progress > UPDATE_SYSUI_FLAGS_THRESHOLD
- ? mTaskToLaunch.getThumbnail().getSysUiStatusNavFlags()
- : 0;
- mLauncher.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
+ mOverviewPanel.setFullscreenProgress(progress);
+ int sysuiFlags = 0;
+ if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
+ TaskView tv = mOverviewPanel.getTaskViewAt(0);
+ if (tv != null) {
+ sysuiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
+ }
}
+ mLauncher.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index ad02de1091..0ee5d047c6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,18 +16,15 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.view.MotionEvent;
+import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
@@ -35,11 +32,12 @@ import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.FlingBlockCheck;
-import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SysUINavigationMode;
@@ -60,6 +58,7 @@ public abstract class TaskViewTouchController
private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
+ private final boolean mIsRtl;
private PendingAnimation mPendingAnimation;
private AnimatorPlaybackController mCurrentAnimation;
@@ -77,10 +76,16 @@ public abstract class TaskViewTouchController
public TaskViewTouchController(T activity) {
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
- mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL);
+ mIsRtl = Utilities.isRtl(activity.getResources());
+ SingleAxisSwipeDetector.Direction dir =
+ mRecentsView.getPagedOrientationHandler().getOppositeSwipeDirection();
+ mDetector = new SingleAxisSwipeDetector(activity, this, dir);
}
private boolean canInterceptTouch() {
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.forceFinishIfCloseToEnd();
+ }
if (mCurrentAnimation != null) {
// If we are already animating from a previous state, we can intercept.
return true;
@@ -93,6 +98,9 @@ public abstract class TaskViewTouchController
protected abstract boolean isRecentsInteractive();
+ /** Is recents view showing a single task in a modal way. */
+ protected abstract boolean isRecentsModal();
+
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
}
@@ -105,6 +113,10 @@ public abstract class TaskViewTouchController
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if ((ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL)
+ && mCurrentAnimation == null) {
+ clearState();
+ }
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch();
if (mNoIntercept) {
@@ -123,8 +135,14 @@ public abstract class TaskViewTouchController
for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
TaskView view = mRecentsView.getTaskViewAt(i);
+
if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
.isEventOverView(view, ev)) {
+ // Disable swiping up and down if the task overlay is modal.
+ if (isRecentsModal()) {
+ mTaskBeingDragged = null;
+ break;
+ }
mTaskBeingDragged = view;
if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
// Don't allow swipe down to open if we don't support swipe up
@@ -181,30 +199,35 @@ public abstract class TaskViewTouchController
mPendingAnimation = null;
}
+ PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
mCurrentAnimationIsGoingUp = goingUp;
BaseDragLayer dl = mActivity.getDragLayer();
- long maxDuration = (long) (2 * dl.getHeight());
-
+ final int secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl);
+ long maxDuration = 2 * secondaryLayerDimension;
+ int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl);
+ int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
if (goingUp) {
mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration);
- mEndDisplacement = -mTaskBeingDragged.getHeight();
+ mEndDisplacement = -secondaryTaskDimension;
} else {
- mPendingAnimation = mRecentsView.createTaskLauncherAnimation(
- mTaskBeingDragged, maxDuration);
- mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
+ mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
+ mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
- mTempCords[1] = mTaskBeingDragged.getHeight();
- dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
- mEndDisplacement = dl.getHeight() - mTempCords[1];
+ // Since the thumbnail is what is filling the screen, based the end displacement on it.
+ View thumbnailView = mTaskBeingDragged.getThumbnail();
+ mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView);
+ dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords);
+ mEndDisplacement = secondaryLayerDimension - mTempCords[1];
}
+ mEndDisplacement *= verticalFactor;
if (mCurrentAnimation != null) {
mCurrentAnimation.setOnCancelRunnable(null);
}
- mCurrentAnimation = AnimatorPlaybackController
- .wrap(mPendingAnimation.anim, maxDuration, this::clearState);
+ mCurrentAnimation = mPendingAnimation.createPlaybackController()
+ .setOnCancelRunnable(this::clearState);
onUserControlledAnimationCreated(mCurrentAnimation);
mCurrentAnimation.getTarget().addListener(this);
mCurrentAnimation.dispatchOnStart();
@@ -212,9 +235,10 @@ public abstract class TaskViewTouchController
}
@Override
- public void onDragStart(boolean start) {
+ public void onDragStart(boolean start, float startDisplacement) {
+ PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
if (mCurrentAnimation == null) {
- reInitAnimationController(mDetector.wasInitialTouchPositive());
+ reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, mIsRtl));
mDisplacementShift = 0;
} else {
mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
@@ -225,9 +249,10 @@ public abstract class TaskViewTouchController
@Override
public boolean onDrag(float displacement) {
+ PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
float totalDisplacement = displacement + mDisplacementShift;
- boolean isGoingUp =
- totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
+ boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp :
+ orientationHandler.isGoingUp(totalDisplacement, mIsRtl);
if (isGoingUp != mCurrentAnimationIsGoingUp) {
reInitAnimationController(isGoingUp);
mFlingBlockCheck.blockFling();
@@ -254,11 +279,12 @@ public abstract class TaskViewTouchController
if (blockedFling) {
fling = false;
}
+ PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
logAction = Touch.FLING;
- boolean goingUp = velocity < 0;
+ boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
logAction = Touch.SWIPE;
@@ -270,26 +296,16 @@ public abstract class TaskViewTouchController
animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
}
- float nextFrameProgress = Utilities.boundToRange(progress
- + velocity * getSingleFrameMs(mActivity) / Math.abs(mEndDisplacement), 0f, 1f);
-
mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
-
- ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
- anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
- anim.setDuration(animationDuration);
- anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- anim.addUpdateListener(valueAnimator -> {
+ mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) {
mRecentsView.redrawLiveTile(true);
}
});
}
- if (QUICKSTEP_SPRINGS.get()) {
- mCurrentAnimation.dispatchOnStartWithVelocity(goingToEnd ? 1f : 0f, velocity);
- }
- anim.start();
+ mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
+ velocity, mEndDisplacement, animationDuration);
}
private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index ad90e1686e..de83caf162 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -15,50 +15,55 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.Animator;
import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
import android.util.Log;
-import android.view.View;
+import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.TransactionCompat;
/**
- * Provider for the atomic remote window animation from the app to the overview.
+ * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview.
*
* @param activity that contains the overview
*/
-final class AppToOverviewAnimationProvider implements
+final class AppToOverviewAnimationProvider> extends
RemoteAnimationProvider {
private static final long RECENTS_LAUNCH_DURATION = 250;
private static final String TAG = "AppToOverviewAnimationProvider";
- private final ActivityControlHelper mHelper;
+ private final BaseActivityInterface, T> mActivityInterface;
// The id of the currently running task that is transitioning to overview.
private final int mTargetTaskId;
+ private final RecentsAnimationDeviceState mDeviceState;
private T mActivity;
private RecentsView mRecentsView;
- AppToOverviewAnimationProvider(ActivityControlHelper helper, int targetTaskId) {
- mHelper = helper;
+ AppToOverviewAnimationProvider(
+ BaseActivityInterface, T> activityInterface, int targetTaskId,
+ RecentsAnimationDeviceState deviceState) {
+ mActivityInterface = activityInterface;
mTargetTaskId = targetTaskId;
+ mDeviceState = deviceState;
}
/**
@@ -70,17 +75,13 @@ final class AppToOverviewAnimationProvider imple
boolean onActivityReady(T activity, Boolean wasVisible) {
activity.getOverviewPanel().showCurrentTask(mTargetTaskId);
AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
- ActivityControlHelper.AnimationFactory factory =
- mHelper.prepareRecentsUI(activity, wasVisible,
- false /* animate activity */, (controller) -> {
+ BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
+ mDeviceState,
+ wasVisible, (controller) -> {
controller.dispatchOnStart();
- ValueAnimator anim = controller.getAnimationPlayer()
- .setDuration(RECENTS_LAUNCH_DURATION);
- anim.setInterpolator(FAST_OUT_SLOW_IN);
- anim.start();
+ controller.getAnimationPlayer().end();
});
- factory.onRemoteAnimationReceived(null);
- factory.createActivityController(RECENTS_LAUNCH_DURATION);
+ factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
factory.setRecentsAttachedToAppWindow(true, false);
mActivity = activity;
mRecentsView = mActivity.getOverviewPanel();
@@ -90,82 +91,75 @@ final class AppToOverviewAnimationProvider imple
/**
* Create remote window animation from the currently running app to the overview panel.
*
- * @param targetCompats the target apps
+ * @param appTargets the target apps
* @return animation from app to overview
*/
@Override
- public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
- if (mRecentsView != null) {
- mRecentsView.setRunningTaskIconScaledDown(true);
- }
- AnimatorSet anim = new AnimatorSet();
- anim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- mHelper.onSwipeUpToRecentsComplete(mActivity);
- if (mRecentsView != null) {
- mRecentsView.animateUpRunningTaskIconScale();
- }
- }
- });
+ public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets) {
+ PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
if (mActivity == null) {
Log.e(TAG, "Animation created, before activity");
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
- return anim;
+ return pa.buildAnim();
}
- RemoteAnimationTargetSet targetSet =
- new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
-
- // Use the top closing app to determine the insets for the animation
- RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mTargetTaskId);
- if (runningTaskTarget == null) {
- Log.e(TAG, "No closing app");
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
- return anim;
- }
-
- final ClipAnimationHelper clipHelper = new ClipAnimationHelper(mActivity);
-
- // At this point, the activity is already started and laid-out. Get the home-bounds
- // relative to the screen using the rootView of the activity.
- int loc[] = new int[2];
- View rootView = mActivity.getRootView();
- rootView.getLocationOnScreen(loc);
- Rect homeBounds = new Rect(loc[0], loc[1],
- loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight());
- clipHelper.updateSource(homeBounds, runningTaskTarget);
-
- Rect targetRect = new Rect();
- mHelper.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity, targetRect);
- clipHelper.updateTargetRect(targetRect);
- clipHelper.prepareAnimation(mActivity.getDeviceProfile(), false /* isOpening */);
-
- ClipAnimationHelper.TransformParams params = new ClipAnimationHelper.TransformParams()
- .setSyncTransactionApplier(new SyncRtSurfaceTransactionApplierCompat(rootView));
- ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
- valueAnimator.setDuration(RECENTS_LAUNCH_DURATION);
- valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
- valueAnimator.addUpdateListener((v) -> {
- params.setProgress((float) v.getAnimatedValue());
- clipHelper.applyTransform(targetSet, params);
+ mRecentsView.setRunningTaskIconScaledDown(true);
+ pa.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mActivityInterface.onSwipeUpToRecentsComplete();
+ mRecentsView.animateUpRunningTaskIconScale();
+ }
});
- if (targetSet.isAnimatingHome()) {
- // If we are animating home, fade in the opening targets
- RemoteAnimationTargetSet openingSet =
- new RemoteAnimationTargetSet(targetCompats, MODE_OPENING);
-
- TransactionCompat transaction = new TransactionCompat();
- valueAnimator.addUpdateListener((v) -> {
- for (RemoteAnimationTargetCompat app : openingSet.apps) {
- transaction.setAlpha(app.leash, (float) v.getAnimatedValue());
- }
- transaction.apply();
- });
+ DepthController depthController = mActivityInterface.getDepthController();
+ if (depthController != null) {
+ pa.addFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mActivity),
+ OVERVIEW.getDepth(mActivity), TOUCH_RESPONSE_INTERPOLATOR);
}
- anim.play(valueAnimator);
- return anim;
+
+ RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
+ wallpaperTargets, MODE_CLOSING);
+
+ // Use the top closing app to determine the insets for the animation
+ RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
+ if (runningTaskTarget == null) {
+ Log.e(TAG, "No closing app");
+ return pa.buildAnim();
+ }
+
+ TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy());
+ tsv.setDp(mActivity.getDeviceProfile());
+ tsv.setPreview(runningTaskTarget);
+ tsv.setLayoutRotation(mRecentsView.getPagedViewOrientedState().getTouchRotation(),
+ mRecentsView.getPagedViewOrientedState().getDisplayRotation());
+
+ TransformParams params = new TransformParams()
+ .setTargetSet(targets)
+ .setSyncTransactionApplier(new SurfaceTransactionApplier(mActivity.getRootView()));
+
+ AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { });
+ params.setBaseBuilderProxy((builder, app, p)
+ -> builder.withAlpha(recentsAlpha.value));
+
+ Interpolator taskInterpolator;
+ if (targets.isAnimatingHome()) {
+ params.setHomeBuilderProxy((builder, app, p) -> builder.withAlpha(1 - p.getProgress()));
+
+ taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR;
+ pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR);
+ } else {
+ // When animation from app to recents, the recents layer is drawn on top of the app. To
+ // prevent the overlap, we animate the task first and then quickly fade in the recents.
+ taskInterpolator = clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0, 0.8f);
+ pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1,
+ clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0.8f, 1));
+ }
+
+ pa.addFloat(params, TransformParams.PROGRESS, 0, 1, taskInterpolator);
+ tsv.addAppToOverviewAnim(pa, taskInterpolator);
+ pa.addOnFrameCallback(() -> tsv.apply(params));
+ return pa.buildAnim();
}
/**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index e5d2b411a8..a63f3a802f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -15,171 +15,96 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
-import android.animation.Animator;
import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
+import android.util.Log;
import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Interpolator;
+import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.launcher3.views.FloatingIconView;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.inputconsumers.InputConsumer;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.launcher3.util.WindowBounds;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import java.util.ArrayList;
import java.util.function.Consumer;
/**
* Base class for swipe up handler with some utility methods
*/
@TargetApi(Build.VERSION_CODES.Q)
-public abstract class BaseSwipeUpHandler
- implements SwipeAnimationListener {
+public abstract class BaseSwipeUpHandler, Q extends RecentsView>
+ extends SwipeUpAnimationLogic implements RecentsAnimationListener {
private static final String TAG = "BaseSwipeUpHandler";
- protected static final Rect TEMP_RECT = new Rect();
- // Start resisting when swiping past this factor of mTransitionDragLength.
- private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
- // This is how far down we can scale down, where 0f is full screen and 1f is recents.
- private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
- private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
-
- // The distance needed to drag to reach the task size in recents.
- protected int mTransitionDragLength;
- // How much further we can drag past recents, as a factor of mTransitionDragLength.
- protected float mDragLengthFactor = 1;
-
- protected final Context mContext;
- protected final OverviewComponentObserver mOverviewComponentObserver;
- protected final ActivityControlHelper mActivityControlHelper;
- protected final RecentsModel mRecentsModel;
- protected final int mRunningTaskId;
-
- protected final ClipAnimationHelper mClipAnimationHelper;
- protected final TransformParams mTransformParams = new TransformParams();
-
- protected final Mode mMode;
-
- // Shift in the range of [0, 1].
- // 0 => preview snapShot is completely visible, and hotseat is completely translated down
- // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
- // visible.
- protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+ protected final BaseActivityInterface, T> mActivityInterface;
+ protected final InputConsumerController mInputConsumer;
protected final ActivityInitListener mActivityInitListener;
- protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
+
+ protected RecentsAnimationController mRecentsAnimationController;
+ protected RecentsAnimationTargets mRecentsAnimationTargets;
+
+ // Callbacks to be made once the recents animation starts
+ private final ArrayList mRecentsAnimationStartCallbacks = new ArrayList<>();
protected T mActivity;
protected Q mRecentsView;
- protected DeviceProfile mDp;
- private final int mPageSpacing;
protected Runnable mGestureEndCallback;
- protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler();
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
- protected int mFinishingRecentsAnimationForNewTaskId = -1;
- protected BaseSwipeUpHandler(Context context,
- OverviewComponentObserver overviewComponentObserver,
- RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
- mContext = context;
- mOverviewComponentObserver = overviewComponentObserver;
- mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
- mRecentsModel = recentsModel;
- mActivityInitListener =
- mActivityControlHelper.createActivityInitListener(this::onActivityInit);
- mRunningTaskId = runningTaskId;
- mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
- this::createNewInputProxyHandler);
- mMode = SysUINavigationMode.getMode(context);
+ private boolean mRecentsViewScrollLinked = false;
- mClipAnimationHelper = new ClipAnimationHelper(context);
- mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
- initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
- .getDeviceProfile(mContext));
+ protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
+ GestureState gestureState, InputConsumerController inputConsumer) {
+ super(context, deviceState, gestureState, new TransformParams());
+ mActivityInterface = gestureState.getActivityInterface();
+ mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
+ mInputConsumer = inputConsumer;
}
- protected void setStateOnUiThread(int stateFlag) {
- if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
- mStateCallback.setState(stateFlag);
- } else {
- postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
- }
+ /**
+ * To be called at the end of constructor of subclasses. This calls various methods which can
+ * depend on proper class initialization.
+ */
+ protected void initAfterSubclassConstructor() {
+ initTransitionEndpoints(
+ mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
}
protected void performHapticFeedback() {
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
}
- public Consumer getRecentsViewDispatcher(RotationMode rotationMode) {
- return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
- }
-
- @UiThread
- public void updateDisplacement(float displacement) {
- // We are moving in the negative x/y direction
- displacement = -displacement;
- float shift;
- if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
- shift = mDragLengthFactor;
- } else {
- float translation = Math.max(displacement, 0);
- shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
- if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
- float pullbackProgress = Utilities.getProgress(shift,
- DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
- pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
- shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
- * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
- }
- }
-
- mCurrentShift.updateValue(shift);
+ public Consumer getRecentsViewDispatcher(float navbarRotation) {
+ return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
}
public void setGestureEndCallback(Runnable gestureEndCallback) {
@@ -189,10 +114,10 @@ public abstract class BaseSwipeUpHandler {
+ SurfaceTransactionApplier.create(mRecentsView, applier -> {
mTransformParams.setSyncTransactionApplier(applier);
- mRecentsAnimationWrapper.runOnInit(() ->
- mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
+ runOnRecentsAnimationStart(() ->
+ mRecentsAnimationTargets.addReleaseCheck(applier));
});
mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
@@ -200,11 +125,13 @@ public abstract class BaseSwipeUpHandler
+ mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
+ mRecentsAnimationTargets));
+ mRecentsViewScrollLinked = true;
}
- protected void startNewTask(int successStateFlag, Consumer resultCallback) {
+ protected void startNewTask(Consumer resultCallback) {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// We finish recents animation inside launchTask() when live tile is enabled.
@@ -212,96 +139,175 @@ public abstract class BaseSwipeUpHandler {
- if (!mCanceled) {
- TaskView nextTask = mRecentsView.getTaskView(taskId);
- if (nextTask != null) {
- nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
- success -> {
- resultCallback.accept(success);
- if (!success) {
- mActivityControlHelper.onLaunchTaskFailed(mActivity);
- nextTask.notifyTaskLaunchFailed(TAG);
- } else {
- mActivityControlHelper.onLaunchTaskSuccess(mActivity);
+ if (!mCanceled) {
+ TaskView nextTask = mRecentsView.getTaskView(taskId);
+ if (nextTask != null) {
+ mGestureState.updateLastStartedTaskId(taskId);
+ boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
+ .contains(taskId);
+ nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+ success -> {
+ resultCallback.accept(success);
+ if (success) {
+ if (hasTaskPreviouslyAppeared) {
+ onRestartPreviouslyAppearedTask();
}
- }, mMainThreadHandler);
- }
- setStateOnUiThread(successStateFlag);
+ } else {
+ mActivityInterface.onLaunchTaskFailed();
+ nextTask.notifyTaskLaunchFailed(TAG);
+ mRecentsAnimationController.finish(true /* toRecents */, null);
+ }
+ }, MAIN_EXECUTOR.getHandler());
}
- mCanceled = false;
- mFinishingRecentsAnimationForNewTaskId = -1;
- });
+ }
+ mCanceled = false;
}
- TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+ }
+
+ /**
+ * Called when we successfully startNewTask() on the task that was previously running. Normally
+ * we call resumeLastTask() when returning to the previously running task, but this handles a
+ * specific edge case: if we switch from A to B, and back to A before B appears, we need to
+ * start A again to ensure it stays on top.
+ */
+ @CallSuper
+ protected void onRestartPreviouslyAppearedTask() {
+ // Finish the controller here, since we won't get onTaskAppeared() for a task that already
+ // appeared.
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.finish(false, null);
+ }
+ }
+
+ /**
+ * Runs the given {@param action} if the recents animation has already started, or queues it to
+ * be run when it is next started.
+ */
+ protected void runOnRecentsAnimationStart(Runnable action) {
+ if (mRecentsAnimationTargets == null) {
+ mRecentsAnimationStartCallbacks.add(action);
+ } else {
+ action.run();
+ }
+ }
+
+ /**
+ * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
+ * @return whether the recents animation has started and there are valid app targets.
+ */
+ protected boolean hasTargets() {
+ return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
}
@Override
- public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
- final Rect overviewStackBounds;
- RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+ public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
+ RecentsAnimationTargets targets) {
+ mRecentsAnimationController = recentsAnimationController;
+ mRecentsAnimationTargets = targets;
+ mTransformParams.setTargetSet(mRecentsAnimationTargets);
+ RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
+ mGestureState.getRunningTaskId());
- if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
- overviewStackBounds = mActivityControlHelper
- .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
- dp = dp.getMultiWindowProfile(mContext, new Point(
- overviewStackBounds.width(), overviewStackBounds.height()));
- } else {
- // If we are not in multi-window mode, home insets should be same as system insets.
- dp = dp.copy(mContext);
- overviewStackBounds = getStackBounds(dp);
- }
- dp.updateInsets(targetSet.homeContentInsets);
- dp.updateIsSeascape(mContext);
if (runningTaskTarget != null) {
- mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+ mTaskViewSimulator.setPreview(runningTaskTarget);
}
- mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
- initTransitionEndpoints(dp);
+ // Only initialize the device profile, if it has not been initialized before, as in some
+ // configurations targets.homeContentInsets may not be correct.
+ if (mActivity == null) {
+ DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
+ if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+ Rect overviewStackBounds = mActivityInterface
+ .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+ dp = dp.getMultiWindowProfile(mContext,
+ new WindowBounds(overviewStackBounds, targets.homeContentInsets));
+ } else {
+ // If we are not in multi-window mode, home insets should be same as system insets.
+ dp = dp.copy(mContext);
+ }
+ dp.updateInsets(targets.homeContentInsets);
+ dp.updateIsSeascape(mContext);
+ initTransitionEndpoints(dp);
+ }
- mRecentsAnimationWrapper.setController(targetSet);
- }
-
- private Rect getStackBounds(DeviceProfile dp) {
- if (mActivity != null) {
- int loc[] = new int[2];
- View rootView = mActivity.getRootView();
- rootView.getLocationOnScreen(loc);
- return new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
- loc[1] + rootView.getHeight());
- } else {
- return new Rect(0, 0, dp.widthPx, dp.heightPx);
+ // Notify when the animation starts
+ if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+ for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+ action.run();
+ }
+ mRecentsAnimationStartCallbacks.clear();
}
}
- protected void initTransitionEndpoints(DeviceProfile dp) {
- mDp = dp;
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
+ if (mRecentsView != null) {
+ mRecentsView.setRecentsAnimationTargets(null, null);
+ }
+ }
- mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
- dp, mContext, TEMP_RECT);
- if (!dp.isMultiWindowMode) {
- // When updating the target rect, also update the home bounds since the location on
- // screen of the launcher window may be stale (position is not updated until first
- // traversal after the window is resized). We only do this for non-multiwindow because
- // we otherwise use the minimized home bounds provided by the system.
- mClipAnimationHelper.updateHomeBounds(getStackBounds(dp));
+ @Override
+ public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ mRecentsAnimationController = null;
+ mRecentsAnimationTargets = null;
+ if (mRecentsView != null) {
+ mRecentsView.setRecentsAnimationTargets(null, null);
}
- mClipAnimationHelper.updateTargetRect(TEMP_RECT);
- if (mMode == Mode.NO_BUTTON) {
- // We can drag all the way to the top of the screen.
- mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
+ }
+
+ @Override
+ public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (mRecentsAnimationController != null) {
+ if (handleTaskAppeared(appearedTaskTarget)) {
+ mRecentsAnimationController.finish(false /* toRecents */,
+ null /* onFinishComplete */);
+ mActivityInterface.onLaunchTaskSuccess();
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+ }
}
}
+ /** @return Whether this was the task we were waiting to appear, and thus handled it. */
+ protected abstract boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget);
+
+ /**
+ * @return The index of the TaskView in RecentsView whose taskId matches the task that will
+ * resume if we finish the controller.
+ */
+ protected int getLastAppearedTaskIndex() {
+ return mGestureState.getLastAppearedTaskId() != -1
+ ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
+ : mRecentsView.getRunningTaskIndex();
+ }
+
+ /**
+ * @return Whether we are continuing a gesture that already landed on a new task,
+ * but before that task appeared.
+ */
+ protected boolean hasStartedNewTask() {
+ return mGestureState.getLastStartedTaskId() != -1;
+ }
+
/**
* Return true if the window should be translated horizontally if the recents view scrolls
*/
protected abstract boolean moveWindowWithRecentsScroll();
- protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome);
+ protected boolean onActivityInit(Boolean alreadyOnHome) {
+ T createdActivity = mActivityInterface.getCreatedActivity();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
+ }
+ if (createdActivity != null) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
+ }
+ initTransitionEndpoints(createdActivity.getDeviceProfile());
+ }
+ return true;
+ }
/**
* Called to create a input proxy for the running task
@@ -321,7 +327,7 @@ public abstract class BaseSwipeUpHandler= end) {
- return 0f;
- }
- return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
- }
-
- @Override
- public void onUpdate(RectF currentRect, float progress) {
- homeAnim.setPlayFraction(progress);
-
- mTransformParams.setProgress(
- Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
- .setCurrentRectAndTargetAlpha(currentRect, getWindowAlpha(progress));
- if (isFloatingIconView) {
- mTransformParams.setCornerRadius(endRadius * progress + startRadius
- * (1f - progress));
- }
- mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
- false /* launcherOnTop */);
-
- if (isFloatingIconView) {
- ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
- windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(),
- false);
- }
- }
-
- @Override
- public void onCancel() {
- if (isFloatingIconView) {
- ((FloatingIconView) floatingView).fastFinish();
- }
- }
- });
- anim.addAnimatorListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- homeAnim.dispatchOnStart();
- }
-
- @Override
- public void onAnimationSuccess(Animator animator) {
- homeAnim.getAnimationPlayer().end();
- }
- });
return anim;
}
public interface Factory {
- BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
- long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
- }
-
- protected interface RunningWindowAnim {
- void end();
-
- void cancel();
-
- static RunningWindowAnim wrap(Animator animator) {
- return new RunningWindowAnim() {
- @Override
- public void end() {
- animator.end();
- }
-
- @Override
- public void cancel() {
- animator.cancel();
- }
- };
- }
-
- static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
- return new RunningWindowAnim() {
- @Override
- public void end() {
- rectFSpringAnim.end();
- }
-
- @Override
- public void cancel() {
- rectFSpringAnim.cancel();
- }
- };
- }
+ BaseSwipeUpHandler newHandler(
+ GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
similarity index 56%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
index 2fa4feb335..6c4c5d3f4d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -21,33 +21,37 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
-import static com.android.launcher3.util.RaceConditionTracker.ENTER;
-import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
+import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
+import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
+import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
+import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.animation.Animator;
-import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Canvas;
import android.graphics.PointF;
-import android.graphics.RectF;
import android.os.Build;
import android.os.SystemClock;
import android.view.View;
@@ -56,47 +60,48 @@ import android.view.ViewTreeObserver.OnDrawListener;
import android.view.WindowInsets;
import android.view.animation.Interpolator;
-import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.statemanager.StatefulActivity;
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.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.ActivityControlHelper.AnimationFactory;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.BaseActivityInterface.AnimationFactory;
+import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
-import com.android.quickstep.util.ClipAnimationHelper.TargetAlphaProvider;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.WindowCallbacksCompat;
+import com.android.systemui.shared.system.TaskInfoCompat;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+/**
+ * Handles the navigation gestures when Launcher is the default home activity.
+ * TODO: Merge this with BaseSwipeUpHandler
+ */
@TargetApi(Build.VERSION_CODES.O)
-public class WindowTransformSwipeHandler
- extends BaseSwipeUpHandler
- implements OnApplyWindowInsetsListener {
- private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
+public abstract class BaseSwipeUpHandlerV2, Q extends RecentsView>
+ extends BaseSwipeUpHandler implements OnApplyWindowInsetsListener {
+ private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
@@ -108,9 +113,11 @@ public class WindowTransformSwipeHandler
}
// Launcher UI related states
- private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
- private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
- private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+ protected static final int STATE_LAUNCHER_PRESENT =
+ getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
+ protected static final int STATE_LAUNCHER_STARTED =
+ getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
+ protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
// Internal initialization states
private static final int STATE_APP_CONTROLLER_RECEIVED =
@@ -122,7 +129,7 @@ public class WindowTransformSwipeHandler
private static final int STATE_SCALED_CONTROLLER_RECENTS =
getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
- private static final int STATE_HANDLER_INVALIDATED =
+ protected static final int STATE_HANDLER_INVALIDATED =
getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_STARTED =
getFlagForIndex(7, "STATE_GESTURE_STARTED");
@@ -133,7 +140,7 @@ public class WindowTransformSwipeHandler
private static final int STATE_CAPTURE_SCREENSHOT =
getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
- private static final int STATE_SCREENSHOT_CAPTURED =
+ protected static final int STATE_SCREENSHOT_CAPTURED =
getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
private static final int STATE_SCREENSHOT_VIEW_SHOWN =
getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
@@ -148,44 +155,7 @@ public class WindowTransformSwipeHandler
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
- public enum GestureEndTarget {
- HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false,
- ContainerType.WORKSPACE, false),
-
- RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
- | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true),
-
- NEW_TASK(0, STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT, false, true,
- ContainerType.APP, true),
-
- LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false);
-
- GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
- int containerType, boolean recentsAttachedToAppWindow) {
- this.endShift = endShift;
- this.endState = endState;
- this.isLauncher = isLauncher;
- this.canBeContinued = canBeContinued;
- this.containerType = containerType;
- this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
- }
-
- /** 0 is app, 1 is overview */
- public final float endShift;
- /** The state to apply when we reach this final target */
- public final int endState;
- /** Whether the target is in the launcher activity */
- public final boolean isLauncher;
- /** Whether the user can start a new gesture while this one is finishing */
- public final boolean canBeContinued;
- /** Used to log where the user ended up after the gesture ends */
- public final int containerType;
- /** Whether RecentsView should be attached to the window as we animate to this target */
- public final boolean recentsAttachedToAppWindow;
- }
-
public static final long MAX_SWIPE_DURATION = 350;
- public static final long MIN_SWIPE_DURATION = 80;
public static final long MIN_OVERSHOOT_DURATION = 120;
public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
@@ -200,7 +170,8 @@ public class WindowTransformSwipeHandler
*/
private static final int LOG_NO_OP_PAGE_INDEX = -1;
- private GestureEndTarget mGestureEndTarget;
+ protected final TaskAnimationManager mTaskAnimationManager;
+
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim mRunningWindowAnim;
private boolean mIsShelfPeeking;
@@ -214,8 +185,6 @@ public class WindowTransformSwipeHandler
private boolean mHasLauncherTransitionControllerStarted;
private AnimationFactory mAnimationFactory = (t) -> { };
- private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
- private boolean mLiveTileOverlayAttached = false;
private boolean mWasLauncherAlreadyVisible;
@@ -229,81 +198,93 @@ public class WindowTransformSwipeHandler
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
- public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
- long touchTimeMs, OverviewComponentObserver overviewComponentObserver,
- boolean continuingLastGesture,
- InputConsumerController inputConsumer, RecentsModel recentsModel) {
- super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+ private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
+
+ public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager, GestureState gestureState,
+ long touchTimeMs, boolean continuingLastGesture,
+ InputConsumerController inputConsumer) {
+ super(context, deviceState, gestureState, inputConsumer);
+ mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
+
+ initAfterSubclassConstructor();
initStateCallbacks();
}
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
this::onLauncherPresentAndGestureStarted);
- mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
this::initializeLauncherAnimationController);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
this::launcherFrameDrawn);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
- mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
- this::sendRemoteAnimationsToAnimationFactory);
-
- mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
+ mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
- mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
+ mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
this::startNewTask);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
this::switchToScreenshot);
- mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+ mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_RECENTS,
this::finishCurrentTransitionToRecents);
- mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+ mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
| STATE_SCALED_CONTROLLER_HOME,
this::finishCurrentTransitionToHome);
- mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
+ mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
this::reset);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
| STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
| STATE_GESTURE_STARTED,
this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
- mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+ mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
+ this::continueComputingRecentsScrollIfNecessary);
+ mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
+ | STATE_RECENTS_SCROLLING_FINISHED,
+ this::onSettledOnEndTarget);
+
+ mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
+ mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
- mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
+ mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
this::notifyTransitionCancelled);
- mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
- mRecentsAnimationWrapper::enableInputConsumer);
+ mGestureState.runOnceAtState(STATE_END_TARGET_SET,
+ () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(),
+ mActivityInterface));
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
+ mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
| STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
(b) -> mRecentsView.setRunningTaskHidden(!b));
}
}
@Override
- protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+ protected boolean onActivityInit(Boolean alreadyOnHome) {
+ super.onActivityInit(alreadyOnHome);
+ final T activity = mActivityInterface.getCreatedActivity();
if (mActivity == activity) {
return true;
}
+
if (mActivity != null) {
// The launcher may have been recreated as a result of device rotation.
int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
@@ -321,47 +302,56 @@ public class WindowTransformSwipeHandler
}
mRecentsView = activity.getOverviewPanel();
- linkRecentsViewScroll();
+ mRecentsView.setOnPageTransitionEndCallback(null);
addLiveTileOverlay();
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
if (alreadyOnHome) {
- onLauncherStart(activity);
+ onLauncherStart();
} else {
- activity.setOnStartCallback(this::onLauncherStart);
+ activity.runOnceOnStart(this::onLauncherStart);
}
setupRecentsViewUi();
+
+ if (mDeviceState.getNavMode() == TWO_BUTTONS) {
+ // If the device is in two button mode, swiping up will show overview with predictions
+ // so we need to kick off switching to the overview predictions as soon as possible
+ mActivityInterface.updateOverviewPredictionState();
+ }
+ linkRecentsViewScroll();
+
return true;
}
@Override
protected boolean moveWindowWithRecentsScroll() {
- return mGestureEndTarget != HOME;
+ return mGestureState.getEndTarget() != HOME;
}
- private void onLauncherStart(final T activity) {
+ private void onLauncherStart() {
+ final T activity = mActivityInterface.getCreatedActivity();
if (mActivity != activity) {
return;
}
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
return;
}
+ mTaskViewSimulator.setRecentsConfiguration(mActivity.getResources().getConfiguration());
// If we've already ended the gesture and are going home, don't prepare recents UI,
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
- if (mGestureEndTarget != HOME) {
+ if (mGestureState.getEndTarget() != HOME) {
Runnable initAnimFactory = () -> {
- mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
- mWasLauncherAlreadyVisible, true,
- this::onAnimatorPlaybackControllerCreated);
+ mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
+ mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
maybeUpdateRecentsAttachedState(false /* animate */);
};
if (mWasLauncherAlreadyVisible) {
// Launcher is visible, but might be about to stop. Thus, if we prepare recents
// now, it might get overridden by moveToRestState() in onStop(). To avoid this,
// wait until the next gesture (and possibly launcher) starts.
- mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
+ mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
} else {
initAnimFactory.run();
}
@@ -372,13 +362,19 @@ public class WindowTransformSwipeHandler
if (mWasLauncherAlreadyVisible) {
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
} else {
- TraceHelper.beginSection("WTS-init");
+ Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
View dragLayer = activity.getDragLayer();
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
+ boolean mHandled = false;
@Override
public void onDraw() {
- TraceHelper.endSection("WTS-init", "Launcher frame is drawn");
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ TraceHelper.INSTANCE.endSection(traceToken);
dragLayer.post(() ->
dragLayer.getViewTreeObserver().removeOnDrawListener(this));
if (activity != mActivity) {
@@ -399,31 +395,52 @@ public class WindowTransformSwipeHandler
// that time by a previous window transition.
setupRecentsViewUi();
+ // For the duration of the gesture, in cases where an activity is launched while the
+ // activity is not yet resumed, finish the animation to ensure we get resumed
+ mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
+ mOnDeferredActivityLaunch);
+
notifyGestureStartedAsync();
}
+ private void onDeferredActivityLaunch() {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mActivityInterface.switchRunningTaskViewToScreenshot(
+ null, () -> {
+ mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+ });
+ } else {
+ mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+ }
+ }
+
private void setupRecentsViewUi() {
if (mContinuingLastGesture) {
updateSysUiFlags(mCurrentShift.value);
return;
}
- mRecentsView.onGestureAnimationStart(mRunningTaskId);
+ notifyGestureAnimationStartToRecents();
+ }
+
+ protected void notifyGestureAnimationStartToRecents() {
+ mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
}
private void launcherFrameDrawn() {
mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
- private void sendRemoteAnimationsToAnimationFactory() {
- mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationWrapper.targetSet);
- }
-
private void initializeLauncherAnimationController() {
buildAnimationController();
+ Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
+ TraceHelper.FLAG_IGNORE_BINDERS);
+ // Only used in debug builds
if (LatencyTrackerCompat.isEnabled(mContext)) {
- LatencyTrackerCompat.logToggleRecents((int) (mLauncherFrameDrawnTime - mTouchTimeMs));
+ LatencyTrackerCompat.logToggleRecents(
+ (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
}
+ TraceHelper.INSTANCE.endSection(traceToken);
// This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
// high-res thumbnail loader here once we are sure that we will end up in an overview state
@@ -431,16 +448,15 @@ public class WindowTransformSwipeHandler
.getHighResLoadingState().setVisible(true);
}
- private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
- float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 +
- mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
- float interpolation = Math.min(1, offsetX / distanceToReachEdge);
- return TaskView.getCurveScaleForInterpolation(interpolation);
- }
-
@Override
public void onMotionPauseChanged(boolean isPaused) {
setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
+
+ if (mDeviceState.isFullyGesturalNavMode() && isPaused) {
+ // In fully gestural nav mode, switch to overview predictions once the user has paused
+ // (this is a no-op if the predictions are already in that state)
+ mActivityInterface.updateOverviewPredictionState();
+ }
}
public void maybeUpdateRecentsAttachedState() {
@@ -455,45 +471,36 @@ public class WindowTransformSwipeHandler
* Note this method has no effect unless the navigation mode is NO_BUTTON.
*/
private void maybeUpdateRecentsAttachedState(boolean animate) {
- if (mMode != Mode.NO_BUTTON || mRecentsView == null) {
+ if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
return;
}
- RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationWrapper.targetSet == null
- ? null
- : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
+ RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
+ ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+ : null;
final boolean recentsAttachedToAppWindow;
- int runningTaskIndex = mRecentsView.getRunningTaskIndex();
- if (mGestureEndTarget != null) {
- recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
+ if (mGestureState.getEndTarget() != null) {
+ recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
} else if (mContinuingLastGesture
&& mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
recentsAttachedToAppWindow = true;
- animate = false;
} else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
// The window is going away so make sure recents is always visible in this case.
recentsAttachedToAppWindow = true;
- animate = false;
} else {
recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
- if (animate) {
- // Only animate if an adjacent task view is visible on screen.
- TaskView adjacentTask1 = mRecentsView.getNextTaskView();
- TaskView adjacentTask2 = mRecentsView.getPreviousTaskView();
- float prevTranslationX = mRecentsView.getTranslationX();
- mRecentsView.setTranslationX(0);
- animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
- || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT));
- mRecentsView.setTranslationX(prevTranslationX);
- }
}
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
}
@Override
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
+ setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
+ }
+
+ private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
mIsLikelyToStartNewTask = isLikelyToStartNewTask;
- maybeUpdateRecentsAttachedState();
+ maybeUpdateRecentsAttachedState(animate);
}
}
@@ -511,13 +518,20 @@ public class WindowTransformSwipeHandler
}
private void buildAnimationController() {
- if (mGestureEndTarget == HOME || mHasLauncherTransitionControllerStarted) {
- // We don't want a new mLauncherTransitionController if mGestureEndTarget == HOME (it
- // has its own animation) or if we're already animating the current controller.
+ if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
return;
}
initTransitionEndpoints(mActivity.getDeviceProfile());
- mAnimationFactory.createActivityController(mTransitionDragLength);
+ mAnimationFactory.createActivityInterface(mTransitionDragLength);
+ }
+
+ /**
+ * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
+ * (it has its own animation) or if we're already animating the current controller.
+ * @return Whether we can create the launcher controller or update its progress.
+ */
+ private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
+ return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
}
@Override
@@ -530,49 +544,41 @@ public class WindowTransformSwipeHandler
private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
mLauncherTransitionController = anim;
mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
- mAnimationFactory.adjustActivityControllerInterpolators();
mLauncherTransitionController.dispatchOnStart();
updateLauncherTransitionProgress();
}
@Override
public Intent getLaunchIntent() {
- return mOverviewComponentObserver.getOverviewIntent();
+ return mGestureState.getOverviewIntent();
}
@Override
public void updateFinalShift() {
-
- SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
- if (controller != null) {
- applyTransformUnchecked();
- updateSysUiFlags(mCurrentShift.value);
- }
-
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- if (mRecentsAnimationWrapper.getController() != null) {
- mLiveTileOverlay.update(mClipAnimationHelper.getCurrentRectWithInsets(),
- mClipAnimationHelper.getCurrentCornerRadius());
+ if (mRecentsAnimationTargets != null) {
+ LiveTileOverlay.INSTANCE.update(
+ mTaskViewSimulator.getCurrentCropRect(),
+ mTaskViewSimulator.getCurrentCornerRadius());
}
}
final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
if (passed != mPassedOverviewThreshold) {
mPassedOverviewThreshold = passed;
- if (mMode != Mode.NO_BUTTON) {
+ if (!mDeviceState.isFullyGesturalNavMode()) {
performHapticFeedback();
}
}
- if (mLauncherTransitionController == null || mLauncherTransitionController
- .getAnimationPlayer().isStarted()) {
- return;
- }
+ updateSysUiFlags(mCurrentShift.value);
+ applyWindowTransform();
updateLauncherTransitionProgress();
}
private void updateLauncherTransitionProgress() {
- if (mGestureEndTarget == HOME) {
+ if (mLauncherTransitionController == null
+ || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
return;
}
// Normalize the progress to 0 to 1, as the animation controller will clamp it to that
@@ -585,40 +591,53 @@ public class WindowTransformSwipeHandler
* @param windowProgress 0 == app, 1 == overview
*/
private void updateSysUiFlags(float windowProgress) {
- if (mRecentsView != null) {
+ if (mRecentsAnimationController != null && mRecentsView != null) {
+ TaskView runningTask = mRecentsView.getRunningTaskView();
TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
int centermostTaskFlags = centermostTask == null ? 0
: centermostTask.getThumbnail().getSysUiStatusNavFlags();
- boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
+ boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
+ boolean quickswitchThresholdPassed = centermostTask != runningTask;
+
// We will handle the sysui flags based on the centermost task view.
- mRecentsAnimationWrapper.setWindowThresholdCrossed(centermostTaskFlags != 0
- || useHomeScreenFlags);
- int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags;
+ mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
+ || (quickswitchThresholdPassed && centermostTaskFlags != 0));
+ mRecentsAnimationController.setSplitScreenMinimized(swipeUpThresholdPassed);
+
+ int sysuiFlags = swipeUpThresholdPassed ? 0 : centermostTaskFlags;
mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
}
}
@Override
- public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
- super.onRecentsAnimationStart(targetSet);
- TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
- setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+ public void onRecentsAnimationStart(RecentsAnimationController controller,
+ RecentsAnimationTargets targets) {
+ ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+ super.onRecentsAnimationStart(controller, targets);
+
+ // Only add the callback to enable the input consumer after we actually have the controller
+ mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
+ mRecentsAnimationController::enableInputConsumer);
+ mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mPassedOverviewThreshold = false;
}
@Override
- public void onRecentsAnimationCanceled() {
- mRecentsAnimationWrapper.setController(null);
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
mActivityInitListener.unregister();
- setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
- TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+
+ // Defer clearing the controller and the targets until after we've updated the state
+ super.onRecentsAnimationCanceled(thumbnailData);
}
@Override
- public void onGestureStarted() {
+ public void onGestureStarted(boolean isLikelyToStartNewTask) {
notifyGestureStartedAsync();
- setStateOnUiThread(STATE_GESTURE_STARTED);
+ setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
}
@@ -641,7 +660,7 @@ public class WindowTransformSwipeHandler
@Override
public void onGestureCancelled() {
updateDisplacement(0);
- setStateOnUiThread(STATE_GESTURE_COMPLETED);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = Touch.SWIPE_NOOP;
handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
}
@@ -656,7 +675,7 @@ public class WindowTransformSwipeHandler
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
- setStateOnUiThread(STATE_GESTURE_COMPLETED);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
@@ -671,16 +690,12 @@ public class WindowTransformSwipeHandler
@Override
protected InputConsumer createNewInputProxyHandler() {
- endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */);
+ endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
endLauncherTransitionController();
- if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- // Hide the task view, if not already hidden
- setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
- }
- BaseDraggingActivity activity = mActivityControlHelper.getCreatedActivity();
- return activity == null
- ? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true);
+ StatefulActivity activity = mActivityInterface.getCreatedActivity();
+ return activity == null ? InputConsumer.NO_OP
+ : new OverviewInputConsumer(mGestureState, activity, null, true);
}
private void endRunningWindowAnim(boolean cancel) {
@@ -693,12 +708,46 @@ public class WindowTransformSwipeHandler
}
}
+ private void onSettledOnEndTarget() {
+ switch (mGestureState.getEndTarget()) {
+ case HOME:
+ mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
+ // Notify swipe-to-home (recents animation) is finished
+ SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+ break;
+ case RECENTS:
+ mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
+ | STATE_SCREENSHOT_VIEW_SHOWN);
+ break;
+ case NEW_TASK:
+ mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
+ break;
+ case LAST_TASK:
+ mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ break;
+ }
+ ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
+ }
+
+ @Override
+ protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+ if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
+ return false;
+ }
+ if (mStateCallback.hasStates(STATE_START_NEW_TASK)
+ && appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
+ reset();
+ return true;
+ }
+ return false;
+ }
+
private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
boolean isCancel) {
final GestureEndTarget endTarget;
final boolean goingToNewTask;
if (mRecentsView != null) {
- if (!mRecentsAnimationWrapper.hasTargets()) {
+ if (!hasTargets()) {
// If there are no running tasks, then we can assume that this is a continuation of
// the last gesture, but after the recents animation has finished
goingToNewTask = true;
@@ -714,7 +763,7 @@ public class WindowTransformSwipeHandler
if (!isFling) {
if (isCancel) {
endTarget = LAST_TASK;
- } else if (mMode == Mode.NO_BUTTON) {
+ } else if (mDeviceState.isFullyGesturalNavMode()) {
if (mIsShelfPeeking) {
endTarget = RECENTS;
} else if (goingToNewTask) {
@@ -735,9 +784,9 @@ public class WindowTransformSwipeHandler
boolean willGoToNewTaskOnSwipeUp =
goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
- if (mMode == Mode.NO_BUTTON && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
+ if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
endTarget = HOME;
- } else if (mMode == Mode.NO_BUTTON && isSwipeUp && !mIsShelfPeeking) {
+ } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
// If swiping at a diagonal, base end target on the faster velocity.
endTarget = NEW_TASK;
} else if (isSwipeUp) {
@@ -748,9 +797,7 @@ public class WindowTransformSwipeHandler