diff --git a/quickstep/res/layout-sw600dp-land/allset_navigation_and_hint.xml b/quickstep/res/layout-sw600dp-land/allset_navigation_and_hint.xml deleted file mode 100644 index 3bfa6da49d..0000000000 --- a/quickstep/res/layout-sw600dp-land/allset_navigation_and_hint.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/quickstep/res/layout-sw600dp/allset_navigation_and_hint.xml b/quickstep/res/layout-sw600dp/allset_navigation_and_hint.xml index 9559072f8f..44b3ecbd97 100644 --- a/quickstep/res/layout-sw600dp/allset_navigation_and_hint.xml +++ b/quickstep/res/layout-sw600dp/allset_navigation_and_hint.xml @@ -21,12 +21,11 @@ style="@style/TextAppearance.GestureTutorial.LinkText" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginBottom="96dp" + android:layout_marginTop="24dp" android:background="?android:attr/selectableItemBackground" android:minHeight="48dp" android:text="@string/allset_navigation_settings" - app:layout_constraintBottom_toTopOf="@id/hint" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/subtitle" app:layout_constraintStart_toStartOf="parent" /> 48dp - 24dp diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml index c96ad11e8b..5899814600 100644 --- a/quickstep/res/values-sw600dp/dimens.xml +++ b/quickstep/res/values-sw600dp/dimens.xml @@ -36,7 +36,6 @@ 120dp - 24dp 38sp 15sp diff --git a/quickstep/res/values-sw720dp-land/dimens.xml b/quickstep/res/values-sw720dp-land/dimens.xml index 4bc8bf3c03..02d11892dd 100644 --- a/quickstep/res/values-sw720dp-land/dimens.xml +++ b/quickstep/res/values-sw720dp-land/dimens.xml @@ -17,7 +17,4 @@ 20dp - - - 24dp diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml index a84b9394b1..585f01e46a 100644 --- a/quickstep/res/values-sw720dp/dimens.xml +++ b/quickstep/res/values-sw720dp/dimens.xml @@ -35,7 +35,6 @@ 64dp - 0dp 42sp 16sp diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 6e3fd32329..cd60879580 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -202,7 +202,6 @@ 40dp - 0dp 36sp 14sp diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml index 868d38bd81..4f0fdf182d 100644 --- a/quickstep/res/values/styles.xml +++ b/quickstep/res/values/styles.xml @@ -152,6 +152,8 @@ @drawable/bg_overview_clear_all_button 96dp 48dp + 12dp + 12dp @null diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java index 08ed60d18a..df867cb831 100644 --- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java @@ -41,8 +41,7 @@ public class FallbackTaskbarUIController extends TaskbarUIController { animateToRecentsState(toState); // Handle tapping on live tile. - RecentsView recentsView = mRecentsActivity.getOverviewPanel(); - recentsView.setTaskLaunchListener(toState == RecentsState.DEFAULT + getRecentsView().setTaskLaunchListener(toState == RecentsState.DEFAULT ? (() -> animateToRecentsState(RecentsState.BACKGROUND_APP)) : null); } }; @@ -88,4 +87,9 @@ public class FallbackTaskbarUIController extends TaskbarUIController { anim.start(); } } + + @Override + public RecentsView getRecentsView() { + return mRecentsActivity.getOverviewPanel(); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 174f97fc34..beab56c6a4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -47,6 +47,7 @@ import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.OnboardingPrefs; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.RecentsAnimationCallbacks; +import com.android.quickstep.views.RecentsView; import java.io.PrintWriter; import java.util.Arrays; @@ -391,4 +392,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw); } + + @Override + public RecentsView getRecentsView() { + return mLauncher.getOverviewPanel(); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 3c9e96f295..9175226598 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -91,6 +91,7 @@ import com.android.launcher3.util.SettingsCache; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.util.ViewCache; import com.android.launcher3.views.ActivityContext; +import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -133,6 +134,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext { private final boolean mIsUserSetupComplete; private final boolean mIsNavBarForceVisible; private final boolean mIsNavBarKidsMode; + private boolean mIsDestroyed = false; // The flag to know if the window is excluded from magnification region computation. private boolean mIsExcludeFromMagnificationRegion = false; @@ -757,42 +759,63 @@ public class TaskbarActivityContext extends BaseTaskbarContext { if (info.isDisabled()) { ItemClickHandler.handleDisabledItemClicked(info, this); } else { - Intent intent = new Intent(info.getIntent()) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { - Toast.makeText(this, R.string.safemode_shortcut_error, - Toast.LENGTH_SHORT).show(); - } else if (info.isPromise()) { - TestLogging.recordEvent( - TestProtocol.SEQUENCE_MAIN, "start: taskbarPromiseIcon"); - intent = new PackageManagerHelper(this) - .getMarketIntent(info.getTargetPackage()) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); + TaskbarUIController taskbarUIController = mControllers.uiController; + RecentsView recents = taskbarUIController.getRecentsView(); + if (recents != null + && taskbarUIController.getRecentsView().isSplitSelectionActive()) { + // If we are selecting a second app for split, launch the split tasks + taskbarUIController.triggerSecondAppForSplit(info, info.intent, view); + } else { + // Else launch the selected task + Intent intent = new Intent(info.getIntent()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { + Toast.makeText(this, R.string.safemode_shortcut_error, + Toast.LENGTH_SHORT).show(); + } else if (info.isPromise()) { + TestLogging.recordEvent( + TestProtocol.SEQUENCE_MAIN, "start: taskbarPromiseIcon"); + intent = new PackageManagerHelper(this) + .getMarketIntent(info.getTargetPackage()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); - } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { - TestLogging.recordEvent( - TestProtocol.SEQUENCE_MAIN, "start: taskbarDeepShortcut"); - String id = info.getDeepShortcutId(); - String packageName = intent.getPackage(); - getSystemService(LauncherApps.class) - .startShortcut(packageName, id, null, null, info.user); - } else { - startItemInfoActivity(info); + } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + TestLogging.recordEvent( + TestProtocol.SEQUENCE_MAIN, "start: taskbarDeepShortcut"); + String id = info.getDeepShortcutId(); + String packageName = intent.getPackage(); + getSystemService(LauncherApps.class) + .startShortcut(packageName, id, null, null, info.user); + } else { + startItemInfoActivity(info); + } + + mControllers.uiController.onTaskbarIconLaunched(info); + } catch (NullPointerException + | ActivityNotFoundException + | SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT) + .show(); + Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e); } - - mControllers.uiController.onTaskbarIconLaunched(info); - mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); - } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { - Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT) - .show(); - Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e); } + mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); } } else if (tag instanceof AppInfo) { - startItemInfoActivity((AppInfo) tag); - mControllers.uiController.onTaskbarIconLaunched((AppInfo) tag); + AppInfo info = (AppInfo) tag; + TaskbarUIController taskbarUIController = mControllers.uiController; + RecentsView recents = taskbarUIController.getRecentsView(); + if (recents != null + && taskbarUIController.getRecentsView().isSplitSelectionActive()) { + // If we are selecting a second app for split, launch the split tasks + taskbarUIController.triggerSecondAppForSplit(info, info.intent, view); + } else { + // Else launch the selected task + startItemInfoActivity((AppInfo) tag); + mControllers.uiController.onTaskbarIconLaunched((AppInfo) tag); + } mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true); } else { Log.e(TAG, "Unknown type clicked: " + tag); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 1014cb6691..4ec9b41ea2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -15,13 +15,18 @@ */ package com.android.launcher3.taskbar; +import android.content.Intent; +import android.graphics.drawable.BitmapDrawable; import android.view.MotionEvent; import android.view.View; import androidx.annotation.CallSuper; +import androidx.annotation.Nullable; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; +import com.android.quickstep.views.RecentsView; +import com.android.quickstep.views.TaskView; import java.io.PrintWriter; import java.util.stream.Stream; @@ -135,4 +140,38 @@ public class TaskbarUIController { prefix, getClass().getSimpleName())); } + + /** + * Returns RecentsView. Overwritten in LauncherTaskbarUIController and + * FallbackTaskbarUIController with Launcher-specific implementations. Returns null for other + * UI controllers (like DesktopTaskbarUIController) that don't have a RecentsView. + */ + public @Nullable RecentsView getRecentsView() { + return null; + } + + /** + * Uses the clicked Taskbar icon to launch a second app for splitscreen. + */ + public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) { + RecentsView recents = getRecentsView(); + TaskView foundTaskView = recents.getTaskViewByComponentName(info.getTargetComponent()); + if (foundTaskView != null) { + recents.confirmSplitSelect( + foundTaskView, + foundTaskView.getTask(), + foundTaskView.getIconView().getDrawable(), + foundTaskView.getThumbnail(), + foundTaskView.getThumbnail().getThumbnail(), + /* intent */ null); + } else { + recents.confirmSplitSelect( + /* containerTaskView */ null, + /* task */ null, + new BitmapDrawable(info.bitmap.icon), + startingView, + /* thumbnail */ null, + intent); + } + } } diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index 5c37da16b5..b476c12a51 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -322,7 +322,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { boolean isRtlEnabled = !mIsRecentsRtl; mPositionHelper.updateThumbnailMatrix( mThumbnailPosition, mThumbnailData, mTaskRect.width(), mTaskRect.height(), - mDp.widthPx, mDp.taskbarSize, mDp.isTablet, + mDp.widthPx, mDp.heightPx, mDp.taskbarSize, mDp.isTablet, mOrientationState.getRecentsActivityRotation(), isRtlEnabled); mPositionHelper.getMatrix().invert(mInversePositionMatrix); if (DEBUG) { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 47374e0262..6286374752 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -74,9 +74,12 @@ import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.WindowConfiguration; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.LocusId; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; @@ -662,8 +665,6 @@ public abstract class RecentsView extends public static final String TAG = "AppsGridAdapter"; private final AppsGridLayoutManager mGridLayoutMgr; + private final List mOnLayoutCompletedListeners = new ArrayList<>(); + + /** + * Listener for {@link RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)} + */ + public interface OnLayoutCompletedListener { + void onLayoutCompleted(); + } + + /** + * Adds a {@link OnLayoutCompletedListener} to receive a callback when {@link + * RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)} is called + */ + public void addOnLayoutCompletedListener(OnLayoutCompletedListener listener) { + mOnLayoutCompletedListeners.add(listener); + } + + /** + * Removes a {@link OnLayoutCompletedListener} to not receive a callback when {@link + * RecyclerView.LayoutManager#onLayoutCompleted(RecyclerView.State)} is called + */ + public void removeOnLayoutCompletedListener(OnLayoutCompletedListener listener) { + mOnLayoutCompletedListeners.remove(listener); + } + public AllAppsGridAdapter(T activityContext, LayoutInflater inflater, AlphabeticalAppsList apps, BaseAdapterProvider[] adapterProviders) { @@ -132,6 +158,14 @@ public class AllAppsGridAdapter extends return extraRows; } + @Override + public void onLayoutCompleted(RecyclerView.State state) { + super.onLayoutCompleted(state); + for (OnLayoutCompletedListener listener : mOnLayoutCompletedListeners) { + listener.onLayoutCompleted(); + } + } + @Override protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) { AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position); diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 4287779c31..3a580203c6 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -323,6 +323,9 @@ public final class FeatureFlags { public static final BooleanFlag SHOW_DOT_PAGINATION = getDebugFlag( "SHOW_DOT_PAGINATION", false, "Enable showing dot pagination in workspace"); + public static final BooleanFlag ENABLE_TOAST_IMPRESSION_LOGGING = getDebugFlag( + "ENABLE_TOAST_IMPRESSION_LOGGING", false, "Enable toast impression logging"); + public static void initialize(Context context) { synchronized (sDebugFlags) { for (DebugFlag flag : sDebugFlags) { diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index af689dc3a3..78e17d835d 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -585,7 +585,6 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { @Override public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo, int desiredStagePosition) { - boolean isLandscape = dp.isLandscape; float topLeftTaskPercent = splitInfo.appsStackedVertically ? splitInfo.topTaskPercent : splitInfo.leftTaskPercent; @@ -593,18 +592,24 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { ? splitInfo.dividerHeightPercent : splitInfo.dividerWidthPercent; + int deviceHeightWithoutTaskbar = dp.availableHeightPx - dp.taskbarSize; + float scale = (float) outRect.height() / deviceHeightWithoutTaskbar; + float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent; + float scaledTopTaskHeight = topTaskHeight * scale; + float dividerHeight = dp.availableHeightPx * dividerBarPercent; + float scaledDividerHeight = dividerHeight * scale; + if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) { - if (isLandscape) { - outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent); + if (splitInfo.appsStackedVertically) { + outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight); } else { - outRect.bottom = outRect.top + Math.round(outRect.height() * topLeftTaskPercent); + outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent); } } else { - if (isLandscape) { - outRect.left += Math.round(outRect.width() - * (topLeftTaskPercent + dividerBarPercent)); + if (splitInfo.appsStackedVertically) { + outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight); } else { - outRect.top += Math.round(outRect.height() + outRect.left += Math.round(outRect.width() * (topLeftTaskPercent + dividerBarPercent)); } } @@ -617,7 +622,7 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx; int totalThumbnailHeight = parentHeight - spaceAboveSnapshot; int dividerBar = Math.round(splitBoundsConfig.appsStackedVertically - ? splitBoundsConfig.dividerHeightPercent * totalThumbnailHeight + ? splitBoundsConfig.dividerHeightPercent * dp.availableHeightPx : splitBoundsConfig.dividerWidthPercent * parentWidth); int primarySnapshotHeight; int primarySnapshotWidth; @@ -641,12 +646,18 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { } secondarySnapshot.setTranslationY(spaceAboveSnapshot); } else { + int deviceHeightWithoutTaskbar = dp.availableHeightPx - dp.taskbarSize; + float scale = (float) totalThumbnailHeight / deviceHeightWithoutTaskbar; + float topTaskHeight = dp.availableHeightPx * taskPercent; + float finalDividerHeight = dividerBar * scale; + float scaledTopTaskHeight = topTaskHeight * scale; primarySnapshotWidth = parentWidth; - primarySnapshotHeight = Math.round(totalThumbnailHeight * taskPercent); + primarySnapshotHeight = Math.round(scaledTopTaskHeight); secondarySnapshotWidth = parentWidth; - secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar; - int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar; + secondarySnapshotHeight = Math.round(totalThumbnailHeight - primarySnapshotHeight + - finalDividerHeight); + float translationY = primarySnapshotHeight + spaceAboveSnapshot + finalDividerHeight; secondarySnapshot.setTranslationY(translationY); FrameLayout.LayoutParams primaryParams =