diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index d747bb8855..6d105ac4fc 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -71,6 +71,7 @@ diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index a06a2dd208..d3606132e5 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -93,6 +93,7 @@ android:name="com.android.quickstep.interaction.GestureSandboxActivity" android:autoRemoveFromRecents="true" android:excludeFromRecents="true" + android:taskAffinity="${packageName}.launcher" android:screenOrientation="portrait"> 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..686189ea81 100644 --- a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml +++ b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml @@ -28,4 +28,9 @@ android:clipToPadding="false" android:outlineProvider="none" android:theme="@style/HomeScreenElementTheme" /> + + + diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/recents_ui_overrides/res/layout/overview_panel.xml index a572cad273..eac0bfa8eb 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 @@ - - - - - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:accessibilityPaneTitle="@string/accessibility_recent_apps" + android:clipChildren="false" + android:clipToPadding="false" + android:theme="@style/HomeScreenElementTheme" + android:visibility="invisible" /> diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml index fe99037ccf..c93cad65bf 100644 --- a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml +++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml @@ -35,25 +35,27 @@ @@ -83,7 +85,7 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:background="?android:attr/selectableItemBackground" - android:text="@string/hotseat_migrate_accept" + android:text="@string/hotseat_edu_accept" android:textAlignment="textEnd" android:textColor="@android:color/white" /> @@ -91,7 +93,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/no_thanks" - android:text="@string/hotseat_migrate_dismiss" + android:text="@string/hotseat_edu_dismiss" android:layout_gravity="start" android:background="?android:attr/selectableItemBackground" android:textColor="@android:color/white" /> diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml index de97d0845e..61c576e82b 100644 --- a/quickstep/recents_ui_overrides/res/values/dimens.xml +++ b/quickstep/recents_ui_overrides/res/values/dimens.xml @@ -23,10 +23,4 @@ 80dp - - - 18dp - 10dp - -60dp - \ No newline at end of file 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 51ee216139..772820719b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -16,7 +16,6 @@ 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; @@ -39,6 +38,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.util.FloatProperty; import android.view.View; import androidx.annotation.NonNull; @@ -50,6 +50,7 @@ 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.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -88,7 +89,8 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets); - AppWindowAnimationHelper helper = new AppWindowAnimationHelper(mLauncher); + AppWindowAnimationHelper helper = + new AppWindowAnimationHelper(recentsView.getPagedViewOrientedState(), mLauncher); anim.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets, helper).setDuration(RECENTS_LAUNCH_DURATION)); @@ -197,7 +199,11 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(), RecentsView.CONTENT_ALPHA, values); case INDEX_RECENTS_TRANSLATE_X_ANIM: - return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), VIEW_TRANSLATE_X) + PagedOrientationHandler orientationHandler = + ((RecentsView)mLauncher.getOverviewPanel()).getPagedViewOrientedState() + .getOrientationHandler(); + FloatProperty translate = orientationHandler.getPrimaryViewTranslate(); + return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), translate) .setDampingRatio(0.8f) .setStiffness(250) .setValues(values) 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 index e7290a4482..a07cd1d82c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -27,11 +27,15 @@ import android.view.ViewGroup; import androidx.core.app.NotificationCompat; +import com.android.launcher3.CellLayout; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; +import com.android.launcher3.Workspace; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.WorkspaceLayoutManager; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.Themes; @@ -61,16 +65,25 @@ public class HotseatEduController { mNotification = createNotification(); } - void migrate() { + boolean migrate() { + Workspace workspace = mLauncher.getWorkspace(); + CellLayout firstScreen = workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID); + int toPage = Workspace.FIRST_SCREEN_ID; + int toRow = mLauncher.getDeviceProfile().inv.numRows - 1; + if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) { + toPage = workspace.getScreenIdForPageIndex(workspace.getPageCount()); + toRow = 0; + } else if (!firstScreen.makeSpaceForHotseatMigration(true)) { + return false; + } ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets(); - int workspacePageCount = mLauncher.getWorkspace().getPageCount(); for (int i = 0; i < hotseatVG.getChildCount(); i++) { View child = hotseatVG.getChildAt(i); ItemInfo tag = (ItemInfo) child.getTag(); mLauncher.getModelWriter().moveItemInDatabase(tag, - LauncherSettings.Favorites.CONTAINER_DESKTOP, workspacePageCount, tag.screenId, - 0); + LauncherSettings.Favorites.CONTAINER_DESKTOP, toPage, tag.screenId, toRow); } + return true; } void removeNotification() { @@ -93,7 +106,7 @@ public class HotseatEduController { private void createNotificationChannel() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; - CharSequence name = mLauncher.getString(R.string.hotseat_migrate_title); + CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title); int importance = NotificationManager.IMPORTANCE_LOW; NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance); @@ -104,8 +117,8 @@ public class HotseatEduController { Intent intent = new Intent(mLauncher.getApplicationContext(), mLauncher.getClass()); intent = new NotificationHandler().addToIntent(intent); - CharSequence name = mLauncher.getString(R.string.hotseat_migrate_prompt_title); - String description = mLauncher.getString(R.string.hotseat_migrate_prompt_content); + CharSequence name = mLauncher.getString(R.string.hotseat_edu_prompt_title); + String description = mLauncher.getString(R.string.hotseat_edu_prompt_content); NotificationCompat.Builder builder = new NotificationCompat.Builder(mLauncher, NOTIFICATION_CHANNEL_ID) .setContentTitle(name) 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 index 8926246ad2..538b7f3952 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -26,6 +26,7 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; +import android.widget.TextView; import android.widget.Toast; import com.android.launcher3.CellLayout; @@ -34,7 +35,9 @@ import com.android.launcher3.Insettable; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.WorkspaceLayoutManager; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -49,12 +52,24 @@ import java.util.List; 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 don't migrate if user has more than SAME_PAGE_MAX_ROWS rows of item in their screen + private static final int SAME_PAGE_MAX_ROWS = 2; + + private static final int MIGRATE_SAME_PAGE = 0; + private static final int MIGRATE_NEW_PAGE = 1; + private static final int MIGRATE_NO_MIGRATE = 2; - public static boolean shown = false; private final Rect mInsets = new Rect(); private View mHotseatWrapper; private CellLayout mSampleHotseat; + private TextView mEduHeading; + private TextView mEduContent; + private Button mDismissBtn; + + private int mMigrationMode = MIGRATE_SAME_PAGE; public void setHotseatEduController(HotseatEduController hotseatEduController) { mHotseatEduController = hotseatEduController; @@ -78,6 +93,8 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable super.onFinishInflate(); mHotseatWrapper = findViewById(R.id.hotseat_wrapper); mSampleHotseat = findViewById(R.id.sample_prediction); + mEduHeading = findViewById(R.id.hotseat_edu_heading); + mEduContent = findViewById(R.id.hotseat_edu_content); DeviceProfile grid = mLauncher.getDeviceProfile(); Rect padding = grid.getHotseatLayoutPadding(); @@ -87,24 +104,27 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable mSampleHotseat.setPadding(padding.left, 0, padding.right, 0); Button turnOnBtn = findViewById(R.id.turn_predictions_on); - turnOnBtn.setOnClickListener(this::onMigrate); + turnOnBtn.setOnClickListener(this::onAccept); - Button learnMoreBtn = findViewById(R.id.no_thanks); - learnMoreBtn.setOnClickListener(this::onKeepDefault); + mDismissBtn = findViewById(R.id.no_thanks); + mDismissBtn.setOnClickListener(this::onDismiss); } - private void onMigrate(View v) { - if (mHotseatEduController == null) return; + private void onAccept(View v) { + if (mMigrationMode == MIGRATE_NO_MIGRATE || !mHotseatEduController.migrate()) { + onDismiss(v); + return; + } handleClose(true); - mHotseatEduController.migrate(); mHotseatEduController.finishOnboarding(); logUserAction(true); - Toast.makeText(mLauncher, R.string.hotseat_items_migrated, Toast.LENGTH_LONG).show(); + int toastStringRes = mMigrationMode == MIGRATE_SAME_PAGE + ? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt; + Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show(); } - private void onKeepDefault(View v) { - if (mHotseatEduController == null) return; + private void onDismiss(View v) { Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show(); mHotseatEduController.finishOnboarding(); logUserAction(false); @@ -148,6 +168,8 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT; target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED : HYBRID_HOTSEAT_CANCELED; + // encoding migration type on pageIndex + target.pageIndex = mMigrationMode; LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null); } @@ -161,6 +183,7 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null); } + private void animateOpen() { if (mIsOpen || mOpenCloseAnimator.isRunning()) { return; @@ -183,17 +206,12 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable handleClose(false); } - /** - * Opens User education dialog with a list of suggested apps - */ - public void show(List predictions) { - if (getParent() != null - || predictions.size() < mLauncher.getDeviceProfile().inv.numHotseatIcons) { - return; - } - attachToContainer(); - logOnBoardingSeen(); - animateOpen(); + @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); @@ -204,6 +222,43 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable } } + @Override + protected void attachToContainer() { + super.attachToContainer(); + if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) { + mEduContent.setText(R.string.hotseat_edu_message_migrate_alt); + mMigrationMode = MIGRATE_NEW_PAGE; + return; + } + CellLayout page = mLauncher.getWorkspace().getScreenWithId( + WorkspaceLayoutManager.FIRST_SCREEN_ID); + + int maxItemsOnPage = SAME_PAGE_MAX_ROWS * mLauncher.getDeviceProfile().inv.numColumns + + (FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0); + if (page.getShortcutsAndWidgets().getChildCount() > maxItemsOnPage + || !page.makeSpaceForHotseatMigration(false)) { + mMigrationMode = MIGRATE_NO_MIGRATE; + mEduContent.setText(R.string.hotseat_edu_message_no_migrate); + mEduHeading.setText(R.string.hotseat_edu_title_no_migrate); + mDismissBtn.setVisibility(GONE); + } + } + + /** + * 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; + } + attachToContainer(); + logOnBoardingSeen(); + animateOpen(); + populatePreview(predictions); + } + /** * Factory method for HotseatPredictionUserEdu dialog */ 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 519939e8d0..9cbe11acc3 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 @@ -80,10 +80,10 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } @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(this::onMotionPauseChanged); 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 ad4a343cb4..19a2bae467 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 @@ -126,7 +126,7 @@ public class NavBarToHomeTouchController implements TouchController, } @Override - public void onDragStart(boolean start) { + public void onDragStart(boolean start, float startDisplacement) { initCurrentAnimation(); } 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 index 34fc3e492c..ab634a4b12 100644 --- 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 @@ -94,8 +94,8 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo } @Override - public void onDragStart(boolean start) { - super.onDragStart(start); + public void onDragStart(boolean start, float startDisplacement) { + super.onDragStart(start, startDisplacement); mReachedOverview = false; } 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 799f1ad182..715529e355 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 @@ -116,12 +116,13 @@ public class NoButtonQuickSwitchTouchController implements TouchController, 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()); mMotionPauseDetector = new MotionPauseDetector(mLauncher); mMotionPauseMinDisplacement = mLauncher.getResources().getDimension( R.dimen.motion_pause_detector_min_displacement_from_app); - mRecentsView = mLauncher.getOverviewPanel(); } @Override 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 912be983f7..d5b221db06 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 @@ -92,8 +92,8 @@ 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() 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 947a8614d2..e0532ac4cf 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 @@ -36,6 +36,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; 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; @@ -77,7 +78,9 @@ public abstract class TaskViewTouchController public TaskViewTouchController(T activity) { mActivity = activity; mRecentsView = activity.getOverviewPanel(); - mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL); + SingleAxisSwipeDetector.Direction dir = + mRecentsView.getPagedOrientationHandler().getOppositeSwipeDirection(); + mDetector = new SingleAxisSwipeDetector(activity, this, dir); } private boolean canInterceptTouch() { @@ -105,6 +108,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) { @@ -125,6 +132,11 @@ public abstract class TaskViewTouchController 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 (view.isTaskOverlayModal()) { + 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,15 +193,18 @@ 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 = (long) (2 * secondaryLayerDimension); + int verticalFactor = -orientationHandler.getTaskDismissDirectionFactor(); + int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged); if (goingUp) { mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged, true /* animateTaskView */, true /* removeTask */, maxDuration); - mEndDisplacement = -mTaskBeingDragged.getHeight(); + mEndDisplacement = -secondaryTaskDimension; } else { mPendingAnimation = mRecentsView.createTaskLaunchAnimation( mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN); @@ -198,6 +213,7 @@ public abstract class TaskViewTouchController dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords); mEndDisplacement = dl.getHeight() - mTempCords[1]; } + mEndDisplacement *= verticalFactor; if (mCurrentAnimation != null) { mCurrentAnimation.setOnCancelRunnable(null); @@ -211,9 +227,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)); mDisplacementShift = 0; } else { mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier; @@ -224,9 +241,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); if (isGoingUp != mCurrentAnimationIsGoingUp) { reInitAnimationController(isGoingUp); mFlingBlockCheck.blockFling(); @@ -253,11 +271,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); goingToEnd = goingUp == mCurrentAnimationIsGoingUp; } else { logAction = Touch.SWIPE; 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 59b117f2e8..375f16013f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -125,7 +125,8 @@ final class AppToOverviewAnimationProvider imple return anim; } - final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper(mActivity); + final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper( + mRecentsView.getPagedViewOrientedState(), 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. 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 3601af2f32..9f19bb301e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -17,7 +17,6 @@ package com.android.quickstep; 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_OVERVIEW_ACTIONS; 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; @@ -32,6 +31,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; +import android.util.Pair; import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; @@ -46,6 +46,10 @@ 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.model.PagedViewOrientedState; +import com.android.launcher3.states.RotationHelper; +import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.touch.PortraitPagedViewHandler; import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory; @@ -75,18 +79,16 @@ public abstract class BaseSwipeUpHandler mActivityInterface; protected final InputConsumerController mInputConsumer; - protected final AppWindowAnimationHelper mAppWindowAnimationHelper; + protected AppWindowAnimationHelper mAppWindowAnimationHelper; protected final TransformParams mTransformParams = new TransformParams(); // Shift in the range of [0, 1]. @@ -123,6 +125,8 @@ public abstract class BaseSwipeUpHandler getRecentsViewDispatcher(RotationMode rotationMode) { - return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null; + public Consumer getRecentsViewDispatcher(RotationMode navBarRotationMode) { + return mRecentsView != null ? mRecentsView.getEventDispatcher(navBarRotationMode) : null; } @UiThread @@ -158,12 +160,12 @@ public abstract class BaseSwipeUpHandler DRAG_LENGTH_FACTOR_START_PULLBACK) { + if (shift > mDragLengthFactorStartPullback) { float pullbackProgress = Utilities.getProgress(shift, - DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor); + mDragLengthFactorStartPullback, mDragLengthFactor); pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress); - shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress - * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK); + shift = mDragLengthFactorStartPullback + pullbackProgress + * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback); } } @@ -326,10 +328,25 @@ public abstract class BaseSwipeUpHandler dragFactorStartAndMaxProgress = + mActivityInterface.getSwipeUpPullbackStartAndMaxProgress(); + mDragLengthFactorStartPullback = dragFactorStartAndMaxProgress.first; + mDragLengthFactorMaxPullback = dragFactorStartAndMaxProgress.second; } } @@ -338,7 +355,17 @@ public abstract class BaseSwipeUpHandler { private Runnable mAdjustInterpolatorsRunnable; + private Pair mSwipeUpPullbackStartAndMaxProgress = + BaseActivityInterface.super.getSwipeUpPullbackStartAndMaxProgress(); @Override public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) { @@ -91,6 +95,11 @@ public final class LauncherActivityInterface implements BaseActivityInterface getSwipeUpPullbackStartAndMaxProgress() { + return mSwipeUpPullbackStartAndMaxProgress; + } + @Override public void onTransitionCancelled(boolean activityVisible) { Launcher launcher = getCreatedActivity(); @@ -176,7 +185,8 @@ public final class LauncherActivityInterface implements BaseActivityInterface( + pullbackStartProgress, pullbackMaxProgress); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 646d01fb8f..3807e45ab9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -261,6 +261,7 @@ public class LauncherSwipeHandler @Override protected boolean onActivityInit(Boolean alreadyOnHome) { + super.onActivityInit(alreadyOnHome); final T activity = mActivityInterface.getCreatedActivity(); if (mActivity == activity) { return true; @@ -770,6 +771,16 @@ public class LauncherSwipeHandler } } + if (endTarget == NEW_TASK) { + SystemUiProxy.INSTANCE.get(mContext).onQuickSwitchToNewTask(); + } + + if (endTarget == RECENTS || endTarget == HOME) { + // Since we're now done quickStepping, we want to only listen for touch events + // for the main orientation's nav bar, instead of multiple + mDeviceState.enableMultipleRegions(false); + } + if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) { return LAST_TASK; } @@ -929,7 +940,8 @@ public class LauncherSwipeHandler mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); } }); - windowAnim.start(velocityPxPerMs); + getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs); + windowAnim.start(mContext, velocityPxPerMs); homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y); mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim); mLauncherTransitionController = null; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java index 3f5179f76c..94b0051a51 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -183,7 +183,8 @@ public final class RecentsActivity extends BaseRecentsActivity { RemoteAnimationTargetCompat[] wallpaperTargets) { AnimatorSet target = new AnimatorSet(); boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING); - AppWindowAnimationHelper helper = new AppWindowAnimationHelper(this); + AppWindowAnimationHelper helper = new AppWindowAnimationHelper( + mFallbackRecentsView.getPagedViewOrientedState(), this); target.play(getRecentsWindowAnimator(taskView, !activityClosing, appTargets, wallpaperTargets, helper).setDuration(RECENTS_LAUNCH_DURATION)); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java index ec7cddfe07..33d9d9aa55 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java @@ -31,6 +31,7 @@ import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; import com.android.quickstep.views.TaskThumbnailView; import com.android.quickstep.views.TaskView; +import com.android.systemui.plugins.OverscrollPlugin; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -67,6 +68,13 @@ public class TaskOverlayFactory implements ResourceBasedOverride { public static final MainThreadInitializedObject INSTANCE = forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class); + /** + * @return a launcher-provided OverscrollPlugin if available, otherwise null + */ + public OverscrollPlugin getLocalOverscrollPlugin() { + return null; + } + public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) { return new TaskOverlay(); } @@ -87,5 +95,12 @@ public class TaskOverlayFactory implements ResourceBasedOverride { * Called when the overlay is no longer used. */ public void reset() { } + + /** + * Whether the overlay is modal, which means only tapping is enabled, but no swiping. + */ + public boolean isOverlayModal() { + return false; + } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 28e8fb6604..4598cdf459 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -17,13 +17,7 @@ package com.android.quickstep; import static android.view.MotionEvent.ACTION_DOWN; -import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; -import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME; -import static com.android.launcher3.config.FeatureFlags.ENABLE_HINTS_IN_OVERVIEW; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI; -import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; -import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; @@ -56,6 +50,8 @@ import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.Launcher; +import com.android.launcher3.PagedView; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.config.FeatureFlags; @@ -80,6 +76,7 @@ import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.util.ProtoTracer; +import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.OverscrollPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.recents.IOverviewProxy; @@ -449,6 +446,8 @@ public class TouchInteractionService extends Service implements PluginListener { } super.applyLoadPlan(tasks); } + + @Override + protected boolean supportsVerticalLandscape() { + return false; + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 8e7074d237..893868bdf8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -176,8 +176,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // Proxy events to recents view if (mPassedWindowMoveSlop && mInteractionHandler != null && !mRecentsViewDispatcher.hasConsumer()) { - mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher( - mNavBarPosition.getRotationMode())); + mRecentsViewDispatcher.setConsumer(mInteractionHandler + .getRecentsViewDispatcher(mNavBarPosition.getRotationMode())); } int edgeFlags = ev.getEdgeFlags(); ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR); @@ -364,7 +364,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC : mNavBarPosition.isLeftEdge() ? -velocityX : velocityY; - mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement); mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY), mDownPos); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java index 5a9c2fe594..91af1563d2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java @@ -15,12 +15,6 @@ */ package com.android.quickstep.util; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; -import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; -import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; -import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; - import android.annotation.TargetApi; import android.content.Context; import android.graphics.Matrix; @@ -28,6 +22,7 @@ import android.graphics.Matrix.ScaleToFit; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; +import android.view.Surface; import androidx.annotation.Nullable; @@ -37,6 +32,7 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.model.PagedViewOrientedState; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.SystemUiProxy; @@ -50,6 +46,12 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat. import com.android.systemui.shared.system.TransactionCompat; import com.android.systemui.shared.system.WindowManagerWrapper; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; +import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; +import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; + /** * Utility class to handle window clip animation */ @@ -82,6 +84,7 @@ public class AppWindowAnimationHelper { private final Rect mTmpRect = new Rect(); private final RectF mTmpRectF = new RectF(); private final RectF mCurrentRectWithInsets = new RectF(); + private PagedViewOrientedState mOrientedState; // Corner radius of windows, in pixels private final float mWindowCornerRadius; // Corner radius of windows when they're in overview mode. @@ -100,20 +103,24 @@ public class AppWindowAnimationHelper { private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a; private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1; - public AppWindowAnimationHelper(Context context) { + public AppWindowAnimationHelper(PagedViewOrientedState orientedState, Context context) { + mOrientedState = orientedState; mWindowCornerRadius = getWindowCornerRadius(context.getResources()); mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources()); mTaskCornerRadius = TaskCornerRadius.get(context); mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows; } + public AppWindowAnimationHelper(Context context) { + this(null, context); + } + private void updateSourceStack(RemoteAnimationTargetCompat target) { mSourceInsets.set(target.contentInsets); mSourceStackBounds.set(target.sourceContainerBounds); // TODO: Should sourceContainerBounds already have this offset? mSourceStackBounds.offsetTo(target.position.x, target.position.y); - } public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) { @@ -138,8 +145,9 @@ public class AppWindowAnimationHelper { // from the source rect. The difference between the target rect (scaled to the // source rect) is the amount to clip on each edge. RectF scaledTargetRect = new RectF(mTargetRect); - Utilities.scaleRectFAboutCenter(scaledTargetRect, - mSourceRect.width() / mTargetRect.width()); + float scale = getSrcToTargetScale(); + Utilities.scaleRectFAboutCenter(scaledTargetRect, scale); + scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top); mSourceWindowClipInsets.set( Math.max(scaledTargetRect.left, 0), @@ -149,6 +157,16 @@ public class AppWindowAnimationHelper { mSourceRect.set(scaledTargetRect); } + private float getSrcToTargetScale() { + if (mOrientedState == null || + (mOrientedState.getDisplayRotation() == Surface.ROTATION_0 + || mOrientedState.getDisplayRotation() == Surface.ROTATION_180)) { + return mSourceRect.width() / mTargetRect.width(); + } else { + return mSourceRect.height() / mTargetRect.height(); + } + } + public void prepareAnimation(DeviceProfile dp, boolean isOpening) { mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING; mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode; @@ -221,7 +239,6 @@ public class AppWindowAnimationHelper { layer = Integer.MAX_VALUE; } } - // Since radius is in Surface space, but we draw the rounded corners in screen space, we // have to undo the scale. surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer, @@ -237,11 +254,16 @@ public class AppWindowAnimationHelper { mTmpRectF.set(mTargetRect); Utilities.scaleRectFAboutCenter(mTmpRectF, params.mOffsetScale); mCurrentRect.set(mRectFEvaluator.evaluate(params.mProgress, mSourceRect, mTmpRectF)); - mCurrentRect.offset(params.mOffsetX, 0); + if (mOrientedState == null || mOrientedState.areMultipleLayoutOrientationsDisabled()) { + mCurrentRect.offset(params.mOffset, 0); + } else { + int displayRotation = mOrientedState.getDisplayRotation(); + mOrientedState.getOrientationHandler().offsetTaskRect(mCurrentRect, + params.mOffset, displayRotation); + } } updateClipRect(params); - return mCurrentRect; } @@ -340,7 +362,7 @@ public class AppWindowAnimationHelper { * @return The source rect's scale and translation relative to the target rect. */ public LauncherState.ScaleAndTranslation getScaleAndTranslation() { - float scale = mSourceRect.width() / mTargetRect.width(); + float scale = getSrcToTargetScale(); float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY(); return new LauncherState.ScaleAndTranslation(scale, 0, translationY); } @@ -390,7 +412,7 @@ public class AppWindowAnimationHelper { public static class TransformParams { private float mProgress; - private float mOffsetX; + private float mOffset; private float mOffsetScale; private @Nullable RectF mCurrentRect; private float mTargetAlpha; @@ -401,7 +423,7 @@ public class AppWindowAnimationHelper { public TransformParams() { mProgress = 0; - mOffsetX = 0; + mOffset = 0; mOffsetScale = 1; mCurrentRect = null; mTargetAlpha = 1; @@ -453,8 +475,8 @@ public class AppWindowAnimationHelper { * the default), then offset the current rect by this amount after computing the rect based * on {@link #mProgress}. */ - public TransformParams setOffsetX(float offsetX) { - mOffsetX = offsetX; + public TransformParams setOffset(float offset) { + mOffset = offset; return this; } @@ -504,8 +526,8 @@ public class AppWindowAnimationHelper { return mProgress; } - public float getOffsetX() { - return mOffsetX; + public float getOffset() { + return mOffset; } public float getOffsetScale() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java index a59c99cf2a..dde76059fe 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java @@ -16,7 +16,7 @@ package com.android.quickstep.util; import android.animation.Animator; -import android.content.res.Resources; +import android.content.Context; import android.graphics.PointF; import android.graphics.RectF; @@ -28,6 +28,8 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.FlingSpringAnim; +import com.android.launcher3.util.DynamicResource; +import com.android.systemui.plugins.ResourceProvider; import java.util.ArrayList; import java.util.List; @@ -103,7 +105,7 @@ public class RectFSpringAnim { private float mMinVisChange; private float mYOvershoot; - public RectFSpringAnim(RectF startRect, RectF targetRect, Resources resources) { + public RectFSpringAnim(RectF startRect, RectF targetRect, Context context) { mStartRect = startRect; mTargetRect = targetRect; mCurrentCenterX = mStartRect.centerX(); @@ -111,8 +113,9 @@ public class RectFSpringAnim { mTrackingBottomY = startRect.bottom < targetRect.bottom; mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top; - mMinVisChange = resources.getDimensionPixelSize(R.dimen.swipe_up_fling_min_visible_change); - mYOvershoot = resources.getDimensionPixelSize(R.dimen.swipe_up_y_overshoot); + ResourceProvider rp = DynamicResource.provider(context); + mMinVisChange = rp.getDimension(R.dimen.swipe_up_fling_min_visible_change); + mYOvershoot = rp.getDimension(R.dimen.swipe_up_y_overshoot); } public void onTargetPositionChanged() { @@ -137,7 +140,12 @@ public class RectFSpringAnim { mAnimatorListeners.add(animatorListener); } - public void start(PointF velocityPxPerMs) { + /** + * Starts the fling/spring animation. + * @param context The activity context. + * @param velocityPxPerMs Velocity of swipe in px/ms. + */ + public void start(Context context, PointF velocityPxPerMs) { // Only tell caller that we ended if both x and y animations have ended. OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> { mRectXAnimEnded = true; @@ -152,7 +160,7 @@ public class RectFSpringAnim { float endX = mTargetRect.centerX(); float minXValue = Math.min(startX, endX); float maxXValue = Math.max(startX, endX); - mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, startX, endX, + mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX, velocityPxPerMs.x * 1000, mMinVisChange, minXValue, maxXValue, 1f, onXEndListener); float startVelocityY = velocityPxPerMs.y * 1000; @@ -162,14 +170,18 @@ public class RectFSpringAnim { float endY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top; float minYValue = Math.min(startY, endY - mYOvershoot); float maxYValue = Math.max(startY, endY); - mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY, + mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, startVelocityY, mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener); float minVisibleChange = Math.abs(1f / mStartRect.height()); + ResourceProvider rp = DynamicResource.provider(context); + float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio); + float stiffness = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness); + mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS) .setSpring(new SpringForce(1f) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) - .setStiffness(SpringForce.STIFFNESS_LOW)) + .setDampingRatio(damping) + .setStiffness(stiffness)) .setStartVelocity(velocityPxPerMs.y * minVisibleChange) .setMaxValue(1f) .setMinimumVisibleChange(minVisibleChange) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index babf13e88d..d2805ed431 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -41,7 +41,9 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.launcher3.graphics.OverviewScrim; +import com.android.launcher3.util.DynamicResource; import com.android.quickstep.views.RecentsView; +import com.android.systemui.plugins.ResourceProvider; /** * Creates an animation where all the workspace items are moved into their final location, @@ -55,9 +57,6 @@ public class StaggeredWorkspaceAnim { private static final float MAX_VELOCITY_PX_PER_S = 22f; - private static final float DAMPING_RATIO = 0.7f; - private static final float STIFFNESS = 150f; - private final float mVelocity; private final float mSpringTransY; @@ -177,9 +176,12 @@ public class StaggeredWorkspaceAnim { v.setTranslationY(mSpringTransY); + ResourceProvider rp = DynamicResource.provider(v.getContext()); + float stiffness = rp.getFloat(R.dimen.staggered_stiffness); + float damping = rp.getFloat(R.dimen.staggered_damping_ratio); ObjectAnimator springTransY = new SpringAnimationBuilder<>(v, VIEW_TRANSLATE_Y) - .setStiffness(STIFFNESS) - .setDampingRatio(DAMPING_RATIO) + .setStiffness(stiffness) + .setDampingRatio(damping) .setMinimumVisibleChange(1f) .setEndValue(0) .setStartVelocity(mVelocity) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java index 9db0c09ae2..d0819c187b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java @@ -21,7 +21,7 @@ import android.util.AttributeSet; import android.util.Property; import android.widget.Button; -import com.android.launcher3.Utilities; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.views.RecentsView.PageCallbacks; import com.android.quickstep.views.RecentsView.ScrollState; @@ -44,21 +44,26 @@ public class ClearAllButton extends Button implements PageCallbacks { private float mContentAlpha = 1; private float mVisibilityAlpha = 1; - private final boolean mIsRtl; + private boolean mIsRtl; private int mScrollOffset; + private RecentsView mParent; public ClearAllButton(Context context, AttributeSet attrs) { super(context, attrs); - mIsRtl = Utilities.isRtl(context.getResources()); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); + mScrollOffset = mIsRtl ? mParent.getPaddingRight() / 2 : - mParent.getPaddingLeft() / 2; + } - RecentsView parent = (RecentsView) getParent(); - mScrollOffset = mIsRtl ? parent.getPaddingRight() / 2 : - parent.getPaddingLeft() / 2; + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mParent = (RecentsView) getParent(); + mIsRtl = !mParent.getPagedOrientationHandler().getRecentsRtlSetting(getResources()); } @Override @@ -73,6 +78,21 @@ public class ClearAllButton extends Button implements PageCallbacks { } } + public void onLayoutChanged() { + if (mParent == null) { + return; + } + setRotation(mParent.getPagedOrientationHandler().getDegreesRotated()); + } + + public void setRtl(boolean rtl) { + if (mIsRtl == rtl) { + return; + } + mIsRtl = rtl; + invalidate(); + } + public void setVisibilityAlpha(float alpha) { if (mVisibilityAlpha != alpha) { mVisibilityAlpha = alpha; @@ -82,14 +102,16 @@ public class ClearAllButton extends Button implements PageCallbacks { @Override public void onPageScroll(ScrollState scrollState) { - float width = getWidth(); - if (width == 0) { + PagedOrientationHandler orientationHandler = mParent.getPagedOrientationHandler(); + float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight()); + if (orientationSize == 0) { return; } - float shift = Math.min(scrollState.scrollFromEdge, width); - setTranslationX(mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift)); - mScrollAlpha = 1 - shift / width; + float shift = Math.min(scrollState.scrollFromEdge, orientationSize); + float translation = mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift); + orientationHandler.setPrimaryAndResetSecondaryTranslate(this, translation); + mScrollAlpha = 1 - shift / orientationSize; updateAlpha(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 3e106aa463..ab8b02f5ff 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -42,10 +42,12 @@ import com.android.launcher3.Hotseat; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.StateListener; +import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.appprediction.PredictionUiStateManager.Client; +import com.android.launcher3.states.RotationHelper; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.ScrimView; @@ -85,6 +87,10 @@ public class LauncherRecentsView extends RecentsView implements StateL } }; + private RotationHelper.ForcedRotationChangedListener mForcedRotationChangedListener = + isForcedRotation -> LauncherRecentsView.this + .disableMultipleLayoutRotations(!isForcedRotation); + public LauncherRecentsView(Context context) { this(context, null); } @@ -180,19 +186,21 @@ public class LauncherRecentsView extends RecentsView implements StateL * @return The translationX to apply to this view so that the first task is just offscreen. */ public float getOffscreenTranslationX(float recentsScale) { - float offscreenX = NORMAL.getOverviewScaleAndTranslation(mActivity).translationX; + LauncherState.ScaleAndTranslation overviewScaleAndTranslation = + NORMAL.getOverviewScaleAndTranslation(mActivity); + float offscreen = mOrientationHandler.getTranslationValue(overviewScaleAndTranslation); // Offset since scale pushes tasks outwards. getTaskSize(sTempRect); - int taskWidth = sTempRect.width(); - offscreenX += taskWidth * (recentsScale - 1) / 2; + int taskSize = mOrientationHandler.getPrimarySize(sTempRect); + offscreen += taskSize * (recentsScale - 1) / 2; if (mRunningTaskTileHidden) { // The first task is hidden, so offset by its width. - offscreenX -= (taskWidth + getPageSpacing()) * recentsScale; + offscreen -= (taskSize + getPageSpacing()) * recentsScale; } if (isRtl()) { - offscreenX = -offscreenX; + offscreen = -offscreen; } - return offscreenX; + return offscreen; } @Override @@ -276,6 +284,11 @@ public class LauncherRecentsView extends RecentsView implements StateL return mTransformParams; } + @Override + protected boolean supportsVerticalLandscape() { + return PagedView.sFlagForcedRotation; + } + @Override public void reset() { super.reset(); @@ -329,6 +342,7 @@ public class LauncherRecentsView extends RecentsView implements StateL super.onAttachedToWindow(); PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener( mRecentsExtraCardPluginListener, RecentsExtraCard.class); + mActivity.getRotationHelper().addForcedRotationCallback(mForcedRotationChangedListener); } @Override @@ -336,22 +350,23 @@ public class LauncherRecentsView extends RecentsView implements StateL super.onDetachedFromWindow(); PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener( mRecentsExtraCardPluginListener); + mActivity.getRotationHelper().removeForcedRotationCallback(mForcedRotationChangedListener); } @Override - protected int computeMinScrollX() { + protected int computeMinScroll() { if (canComputeScrollX() && !mIsRtl) { return computeScrollX(); } - return super.computeMinScrollX(); + return super.computeMinScroll(); } @Override - protected int computeMaxScrollX() { + protected int computeMaxScroll() { if (canComputeScrollX() && mIsRtl) { return computeScrollX(); } - return super.computeMaxScrollX(); + return super.computeMaxScroll(); } private boolean canComputeScrollX() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index bc89859329..872e690acc 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -22,7 +22,6 @@ import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAG import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; @@ -30,6 +29,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS; import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; @@ -52,7 +52,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; @@ -66,11 +65,14 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.util.FloatProperty; +import android.util.Property; import android.util.SparseBooleanArray; +import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.OrientationEventListener; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; @@ -80,26 +82,32 @@ import android.view.animation.Interpolator; import android.widget.ListView; import androidx.annotation.Nullable; -import androidx.dynamicanimation.animation.SpringForce; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; +import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.PagedView; 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.PropertyListBuilder; import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.states.RotationHelper; +import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.PendingAnimation; import com.android.launcher3.util.Themes; @@ -108,12 +116,14 @@ import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; +import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.ViewUtils; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.LayoutUtils; +import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -162,6 +172,8 @@ public abstract class RecentsView extends PagedView impl } }; + private final OrientationEventListener mOrientationListener; + private int mPreviousRotation; protected RecentsAnimationController mRecentsAnimationController; protected RecentsAnimationTargets mRecentsAnimationTargets; protected AppWindowAnimationHelper mAppWindowAnimationHelper; @@ -198,7 +210,7 @@ public abstract class RecentsView extends PagedView impl private boolean mDwbToastShown; protected boolean mDisallowScrollToClearAll; private boolean mOverlayEnabled; - private boolean mFreezeViewVisibility; + protected boolean mFreezeViewVisibility; /** * TODO: Call reloadIdNeeded in onTaskStackChanged. @@ -268,15 +280,8 @@ public abstract class RecentsView extends PagedView impl } }; - private final IPinnedStackAnimationListener mIPinnedStackAnimationListener = - new IPinnedStackAnimationListener.Stub() { - @Override - public void onPinnedStackAnimationStarted() { - // Needed for activities that auto-enter PiP, which will not trigger a remote - // animation to be created - mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); - } - }; + private final PinnedStackAnimationListener mIPinnedStackAnimationListener = + new PinnedStackAnimationListener(); // Used to keep track of the last requested task list id, so that we do not request to load the // tasks again if we have already requested it and the task list has not changed @@ -321,6 +326,8 @@ public abstract class RecentsView extends PagedView impl // Keeps track of the index where the first TaskView should be private int mTaskViewStartIndex = 0; + private View mActionsView; + private boolean mGestureRunning = false; private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener = (inMultiWindowMode) -> { @@ -340,7 +347,8 @@ public abstract class RecentsView extends PagedView impl mActivity = (T) BaseActivity.fromContext(context); mModel = RecentsModel.INSTANCE.get(context); mIdp = InvariantDeviceProfile.INSTANCE.get(context); - mTempAppWindowAnimationHelper = new AppWindowAnimationHelper(context); + mTempAppWindowAnimationHelper = + new AppWindowAnimationHelper(getPagedViewOrientedState(), context); mClearAllButton = (ClearAllButton) LayoutInflater.from(context) .inflate(R.layout.overview_clear_all_button, this, false); @@ -348,7 +356,7 @@ public abstract class RecentsView extends PagedView impl mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */, 10 /* initial size */); - mIsRtl = !Utilities.isRtl(getResources()); + mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources()); setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); mTaskTopMargin = getResources() .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); @@ -368,9 +376,26 @@ public abstract class RecentsView extends PagedView impl .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); setWillNotDraw(false); updateEmptyMessage(); + disableMultipleLayoutRotations(!supportsVerticalLandscape()); // Initialize quickstep specific cache params here, as this is constructed only once mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5); + mOrientationListener = new OrientationEventListener(getContext()) { + @Override + public void onOrientationChanged(int i) { + int rotation = RotationHelper.getRotationFromDegrees(i); + if (mPreviousRotation != rotation) { + animateRecentsRotationInPlace(rotation); + if (rotation == 0) { + showActionsView(); + } else { + hideActionsView(); + } + mPreviousRotation = rotation; + } + } + }; + } public OverScroller getScroller() { @@ -451,8 +476,10 @@ public abstract class RecentsView extends PagedView impl mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this); RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); mIdp.addOnChangeListener(this); + mIPinnedStackAnimationListener.setActivity(mActivity); SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener( mIPinnedStackAnimationListener); + addActionsView(); } @Override @@ -466,6 +493,7 @@ public abstract class RecentsView extends PagedView impl RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); mIdp.removeOnChangeListener(this); SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null); + mIPinnedStackAnimationListener.setActivity(null); } @Override @@ -496,6 +524,13 @@ public abstract class RecentsView extends PagedView impl } public void setOverviewStateEnabled(boolean enabled) { + if (supportsVerticalLandscape() && mOrientationListener.canDetectOrientation()) { + if (enabled) { + mOrientationListener.enable(); + } else { + mOrientationListener.disable(); + } + } mOverviewStateEnabled = enabled; updateTaskStackListenerState(); if (!enabled) { @@ -573,6 +608,13 @@ public abstract class RecentsView extends PagedView impl return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev); } + @Override + protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { + // Enables swiping to the left or right only if the task overlay is not modal. + if (getCurrentPageTaskView() == null || !getCurrentPageTaskView().isTaskOverlayModal()) { + super.determineScrollingStart(ev, touchSlopScale); + } + } protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) { return true; } @@ -599,6 +641,7 @@ public abstract class RecentsView extends PagedView impl if (getTaskViewCount() != requiredTaskCount) { if (indexOfChild(mClearAllButton) != -1) { removeView(mClearAllButton); + hideActionsView(); } for (int i = getTaskViewCount(); i < requiredTaskCount; i++) { addView(mTaskViewPool.getView()); @@ -608,6 +651,7 @@ public abstract class RecentsView extends PagedView impl } if (requiredTaskCount > 0) { addView(mClearAllButton); + showActionsView(); } } @@ -616,7 +660,7 @@ public abstract class RecentsView extends PagedView impl final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex; final Task task = tasks.get(i); final TaskView taskView = (TaskView) getChildAt(pageIndex); - taskView.bind(task); + taskView.bind(task, mLayoutRotation); } if (mNextPage == INVALID_PAGE) { @@ -647,6 +691,7 @@ public abstract class RecentsView extends PagedView impl if (indexOfChild(mClearAllButton) != -1) { removeView(mClearAllButton); } + hideActionsView(); } public int getTaskViewCount() { @@ -748,19 +793,21 @@ public abstract class RecentsView extends PagedView impl if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) { return; } - int scrollX = getScrollX(); - final int halfPageWidth = getNormalChildWidth() / 2; - final int screenCenter = mInsets.left + getPaddingLeft() + scrollX + halfPageWidth; - final int halfScreenWidth = getMeasuredWidth() / 2; + CurveProperties curveProperties = mOrientationHandler + .getCurveProperties(this, mInsets); + int scroll = curveProperties.scroll; + final int halfPageSize = curveProperties.halfPageSize; + final int screenCenter = curveProperties.screenCenter; + final int halfScreenSize = curveProperties.halfScreenSize; final int pageSpacing = mPageSpacing; - mScrollState.scrollFromEdge = mIsRtl ? scrollX : (mMaxScrollX - scrollX); + mScrollState.scrollFromEdge = mIsRtl ? scroll : (mMaxScroll - scroll); final int pageCount = getPageCount(); for (int i = 0; i < pageCount; i++) { View page = getPageAt(i); - float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth; + float pageCenter = mOrientationHandler.getViewCenterPosition(page) + halfPageSize; float distanceFromScreenCenter = screenCenter - pageCenter; - float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing; + float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing; mScrollState.linearInterpolation = Math.min(1, Math.abs(distanceFromScreenCenter) / distanceToReachEdge); ((PageCallbacks) page).onPageScroll(mScrollState); @@ -892,6 +939,7 @@ public abstract class RecentsView extends PagedView impl setEnableDrawingLiveTile(false); setRunningTaskHidden(true); setRunningTaskIconScaledDown(true); + mGestureRunning = true; } /** @@ -908,6 +956,47 @@ public abstract class RecentsView extends PagedView impl setSwipeDownShouldLaunchApp(true); } + private void animateRecentsRotationInPlace(int newRotation) { + if (!supportsVerticalLandscape()) { + return; + } + + AnimatorSet pa = setRecentsChangedOrientation(true); + pa.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + updateLayoutRotation(newRotation); + ((DragLayer)mActivity.getDragLayer()).recreateControllers(); + rotateAllChildTasks(); + setRecentsChangedOrientation(false).start(); + } + }); + pa.start(); + } + + public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) { + getRunningTaskIndex(); + int runningIndex = getCurrentPage(); + AnimatorSet as = new AnimatorSet(); + for (int i = 0; i < getTaskViewCount(); i++) { + if (runningIndex == i) { + continue; + } + View taskView = getTaskViewAt(i); + as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1)); + } + return as; + } + + abstract protected boolean supportsVerticalLandscape(); + + private void rotateAllChildTasks() { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView taskView = getTaskViewAt(i); + taskView.setOverviewRotation(mLayoutRotation); + } + } + /** * Called when a gesture from an app has finished. */ @@ -920,6 +1009,7 @@ public abstract class RecentsView extends PagedView impl } setRunningTaskHidden(false); animateUpRunningTaskIconScale(); + mGestureRunning = false; } /** @@ -936,6 +1026,7 @@ public abstract class RecentsView extends PagedView impl addView(taskView, mTaskViewStartIndex); if (wasEmpty) { addView(mClearAllButton); + showActionsView(); } // The temporary running task is only used for the duration between the start of the // gesture and the task list is loaded and applied @@ -943,7 +1034,7 @@ public abstract class RecentsView extends PagedView impl new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0, false, true, false, false, new ActivityManager.TaskDescription(), 0, new ComponentName("", ""), false); - taskView.bind(mTmpRunningTask); + taskView.bind(mTmpRunningTask, mLayoutRotation); } boolean runningTaskTileHidden = mRunningTaskTileHidden; @@ -1119,15 +1210,22 @@ public abstract class RecentsView extends PagedView impl private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) { addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim); + FloatProperty secondaryViewTranslate = + mOrientationHandler.getSecondaryViewTranslate(); + int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); + int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor(); if (UNSTABLE_SPRINGS.get() && taskView instanceof TaskView) { - addAnim(new SpringObjectAnimator<>(taskView, VIEW_TRANSLATE_Y, - MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY, - SpringForce.STIFFNESS_MEDIUM, - 0, -taskView.getHeight()), + ResourceProvider rp = DynamicResource.provider(mActivity); + float dampingRatio = rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio); + float stiffness = rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness); + + addAnim(new SpringObjectAnimator<>(taskView, secondaryViewTranslate, + MIN_VISIBLE_CHANGE_PIXELS, dampingRatio, + stiffness, 0, verticalFactor * secondaryTaskDimension), duration, LINEAR, anim); } else { - addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()), - duration, LINEAR, anim); + addAnim(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate, + verticalFactor * secondaryTaskDimension), duration, LINEAR, anim); } } @@ -1158,11 +1256,9 @@ public abstract class RecentsView extends PagedView impl } int[] oldScroll = new int[count]; - getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); - int[] newScroll = new int[count]; + getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView); - int taskCount = getTaskViewCount(); int scrollDiffPerPage = 0; if (count > 1) { @@ -1200,13 +1296,17 @@ public abstract class RecentsView extends PagedView impl int scrollDiff = newScroll[i] - oldScroll[i] + offset; if (scrollDiff != 0) { if (UNSTABLE_SPRINGS.get() && child instanceof TaskView) { + ResourceProvider rp = DynamicResource.provider(mActivity); + float damping = rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio); + float stiffness = rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness); + addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X, - MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY, - SpringForce.STIFFNESS_MEDIUM, - 0, scrollDiff), duration, ACCEL, anim); + MIN_VISIBLE_CHANGE_PIXELS, damping, + stiffness, 0, scrollDiff), duration, ACCEL, anim); } else { - addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration, - ACCEL, anim); + Property translationProperty = mOrientationHandler.getPrimaryViewTranslate(); + addAnim(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff), + duration, ACCEL, anim); } needsCurveUpdates = true; @@ -1252,6 +1352,7 @@ public abstract class RecentsView extends PagedView impl if (getTaskViewCount() == 0) { removeView(mClearAllButton); + hideActionsView(); startHome(); } else { snapToPageImmediately(pageToSnapTo); @@ -1396,15 +1497,17 @@ public abstract class RecentsView extends PagedView impl } } mClearAllButton.setContentAlpha(mContentAlpha); - int alphaInt = Math.round(alpha * 255); mEmptyMessagePaint.setAlpha(alphaInt); mEmptyIcon.setAlpha(alphaInt); - if (alpha > 0) { setVisibility(VISIBLE); + if (!mGestureRunning) { + showActionsView(); + } } else if (!mFreezeViewVisibility) { setVisibility(GONE); + hideActionsView(); } } @@ -1418,10 +1521,27 @@ public abstract class RecentsView extends PagedView impl if (!mFreezeViewVisibility) { setVisibility(mContentAlpha > 0 ? VISIBLE : GONE); + if (mContentAlpha > 0) { + showActionsView(); + } else { + hideActionsView(); + } } } } + @Override + public void setLayoutRotation(int touchRotation, int displayRotation) { + if (!sFlagForcedRotation) { + return; + } + + super.setLayoutRotation(touchRotation, displayRotation); + mClearAllButton.onLayoutChanged(); + mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources()); + setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); + } + @Override public void onViewAdded(View child) { super.onViewAdded(child); @@ -1642,7 +1762,8 @@ public abstract class RecentsView extends PagedView impl } }); - AppWindowAnimationHelper appWindowAnimationHelper = new AppWindowAnimationHelper(mActivity); + AppWindowAnimationHelper appWindowAnimationHelper = new AppWindowAnimationHelper( + getPagedViewOrientedState(), mActivity); appWindowAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this); appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */); AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper); @@ -1799,7 +1920,7 @@ public abstract class RecentsView extends PagedView impl } @Override - protected int computeMinScrollX() { + protected int computeMinScroll() { if (getTaskViewCount() > 0) { if (mDisallowScrollToClearAll) { // We aren't showing the clear all button, @@ -1814,11 +1935,11 @@ public abstract class RecentsView extends PagedView impl } return getScrollForPage(mTaskViewStartIndex); } - return super.computeMinScrollX(); + return super.computeMinScroll(); } @Override - protected int computeMaxScrollX() { + protected int computeMaxScroll() { if (getTaskViewCount() > 0) { if (mDisallowScrollToClearAll) { // We aren't showing the clear all button, @@ -1833,7 +1954,7 @@ public abstract class RecentsView extends PagedView impl } return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1); } - return super.computeMaxScrollX(); + return super.computeMaxScroll(); } public ClearAllButton getClearAllButton() { @@ -1848,31 +1969,30 @@ public abstract class RecentsView extends PagedView impl return 0; } int startScroll = getScrollForPage(getRunningTaskIndex()); - int offsetX = startScroll - getScrollX(); - offsetX *= getScaleX(); + int offsetX = startScroll - mOrientationHandler.getPrimaryScroll(this); + offsetX *= mOrientationHandler.getPrimaryScale(this); return offsetX; } - public Consumer getEventDispatcher(RotationMode rotationMode) { - if (rotationMode.isTransposed) { - Matrix transform = new Matrix(); - transform.setRotate(-rotationMode.surfaceRotation); - - if (getWidth() > 0 && getHeight() > 0) { - float scale = ((float) getWidth()) / getHeight(); - transform.postScale(scale, 1 / scale); - } - - Matrix inverse = new Matrix(); - transform.invert(inverse); - return e -> { - e.transform(transform); - super.onTouchEvent(e); - e.transform(inverse); - }; + public Consumer getEventDispatcher(RotationMode navBarRotationMode) { + float degreesRotated; + if (navBarRotationMode == RotationMode.NORMAL) { + degreesRotated = RotationHelper.getDegreesFromRotation(mLayoutRotation); } else { + degreesRotated = -navBarRotationMode.surfaceRotation; + } + if (degreesRotated == 0) { return super::onTouchEvent; } + + // At this point the event coordinates have already been transformed, so we need to + // undo that transformation since PagedView also accommodates for the transformation via + // PagedOrientationHandler + return e -> { + RotationHelper.transformEvent(-degreesRotated, e, true); + super.onTouchEvent(e); + RotationHelper.transformEvent(-degreesRotated, e, false); + }; } public AppWindowAnimationHelper getClipAnimationHelper() { @@ -1955,4 +2075,51 @@ public abstract class RecentsView extends PagedView impl /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */ void onEmptyMessageUpdated(boolean isEmpty); } + + + private static class PinnedStackAnimationListener extends + IPinnedStackAnimationListener.Stub { + private T mActivity; + + public void setActivity(T activity) { + mActivity = activity; + } + + @Override + public void onPinnedStackAnimationStarted() { + // Needed for activities that auto-enter PiP, which will not trigger a remote + // animation to be created + mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); + } + } + + private void showActionsView() { + if (mActionsView != null && getTaskViewCount() > 0) { + mActionsView.setVisibility(VISIBLE); + } + } + + private void hideActionsView() { + if (mActionsView != null) { + mActionsView.setVisibility(GONE); + } + } + + private void addActionsView() { + if (mActionsView == null && ENABLE_OVERVIEW_ACTIONS.get() + && SysUINavigationMode.removeShelfFromOverview(mActivity)) { + mActionsView = ((ViewGroup) getParent()).findViewById(R.id.overview_actions_view); + if (mActionsView != null) { + Rect rect = new Rect(); + getTaskSize(rect); + InsettableFrameLayout.LayoutParams layoutParams = + new InsettableFrameLayout.LayoutParams(rect.width(), + getResources().getDimensionPixelSize( + R.dimen.overview_actions_height)); + layoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + mActionsView.setLayoutParams(layoutParams); + showActionsView(); + } + } + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java index 8ed139269a..178ff32b4a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java @@ -36,6 +36,7 @@ import android.graphics.RectF; import android.graphics.Shader; import android.util.AttributeSet; import android.util.FloatProperty; +import android.util.Log; import android.util.Property; import android.view.Surface; import android.view.View; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index dce92ff390..e09e01ff9f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -16,6 +16,12 @@ package com.android.quickstep.views; +import static android.view.Gravity.BOTTOM; +import static android.view.Gravity.CENTER_HORIZONTAL; +import static android.view.Gravity.CENTER_VERTICAL; +import static android.view.Gravity.END; +import static android.view.Gravity.START; +import static android.view.Gravity.TOP; import static android.widget.Toast.LENGTH_SHORT; import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION; @@ -23,7 +29,6 @@ import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -41,25 +46,24 @@ import android.os.Handler; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; -import android.view.Gravity; +import android.view.Surface; import android.view.View; import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.Toast; -import androidx.annotation.Nullable; - import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.states.RotationHelper; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; @@ -166,8 +170,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private final float mWindowCornerRadius; private final BaseDraggingActivity mActivity; - @Nullable private View mActionsView; - private ObjectAnimator mIconAndDimAnimator; private float mIconScaleAnimStartProgress = 0; private float mFocusTransitionProgress = 1; @@ -237,18 +239,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { TaskView.LayoutParams thumbnailParams = (LayoutParams) mSnapshotView.getLayoutParams(); thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(context); mSnapshotView.setLayoutParams(thumbnailParams); + } - - if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) { - mActionsView = mSnapshotView.getTaskOverlay().getActionsView(); - if (mActionsView != null) { - TaskView.LayoutParams params = new TaskView.LayoutParams(LayoutParams.MATCH_PARENT, - getResources().getDimensionPixelSize(R.dimen.overview_actions_height), - Gravity.BOTTOM); - addView(mActionsView, params); - mActionsView.setAlpha(0); - } - } + public boolean isTaskOverlayModal() { + return mSnapshotView.getTaskOverlay().isOverlayModal(); } public TaskMenuView getMenuView() { @@ -262,10 +256,11 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { /** * Updates this task view to the given {@param task}. */ - public void bind(Task task) { + public void bind(Task task, int recentsRotation) { cancelPendingLoadTasks(); mTask = task; mSnapshotView.bind(task); + setOverviewRotation(recentsRotation); } public Task getTask() { @@ -435,6 +430,42 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } } + void setOverviewRotation(int iconRotation) { + PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler(); + boolean isRtl = orientationHandler.getRecentsRtlSetting(getResources()); + LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams(); + snapshotParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getContext()); + int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin); + LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams(); + int rotation = RotationHelper.getDegreesFromRotation(iconRotation); + switch (iconRotation) { + case Surface.ROTATION_90: + iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL; + iconParams.rightMargin = -thumbnailPadding; + iconParams.leftMargin = iconParams.topMargin = iconParams.bottomMargin = 0; + break; + case Surface.ROTATION_180: + iconParams.gravity = BOTTOM | CENTER_HORIZONTAL; + iconParams.bottomMargin = -thumbnailPadding; + iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0; + break; + case Surface.ROTATION_270: + iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL; + iconParams.leftMargin = -thumbnailPadding; + iconParams.rightMargin = iconParams.topMargin = iconParams.bottomMargin = 0; + break; + case Surface.ROTATION_0: + default: + iconParams.gravity = TOP | CENTER_HORIZONTAL; + iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = + iconParams.bottomMargin = 0; + break; + } + mSnapshotView.setLayoutParams(snapshotParams); + mIconView.setLayoutParams(iconParams); + mIconView.setRotation(rotation); + } + private void setIconAndDimTransitionProgress(float progress, boolean invert) { if (invert) { progress = 1 - progress; @@ -449,11 +480,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mIconView.setScaleX(scale); mIconView.setScaleY(scale); - - if (mActionsView != null && isRunningTask()) { - mActionsView.setAlpha(scale); - } - mFooterVerticalOffset = 1.0f - scale; for (FooterWrapper footer : mFooters) { if (footer != null) { @@ -544,31 +570,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mMenuView.setScaleX(getScaleX()); mMenuView.setScaleY(getScaleY()); } - - // This is not the proper implementation and will be replaced with a proper layout. - if (mActionsView != null) { - if (mFocusTransitionProgress == 1f) { - mActionsView.setAlpha(1 - curveInterpolation / MAX_PAGE_SCRIM_ALPHA); - } - maintainActionViewPosition(curveScaleForCurveInterpolation); - } - } - private void maintainActionViewPosition(float curveScaleForCurveInterpolation) { - float inverseCurveScaleFactor = curveScaleForCurveInterpolation == 0 ? 0 : - (1f / curveScaleForCurveInterpolation); - mActionsView.setScaleX(inverseCurveScaleFactor); - mActionsView.setScaleY(inverseCurveScaleFactor); - mActionsView.setTranslationX(inverseCurveScaleFactor * (-getX() - + getRecentsView().getScrollX() + getRecentsView().scrollOffsetLeft())); - mActionsView.setTranslationY( - (1f - curveScaleForCurveInterpolation) * (mSnapshotView.getHeight() - + mActionsView.getHeight()) / 2f - + inverseCurveScaleFactor * (-getTranslationY())); - } - - /** * Sets the footer at the specific index and returns the previously set footer. */ @@ -597,8 +600,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { addView(view, indexToAdd); LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); - layoutParams.gravity = - Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL; layoutParams.bottomMargin = ((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin; view.setAlpha(mFooterAlpha); @@ -851,9 +853,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mFullscreenProgress = progress; boolean isFullscreen = mFullscreenProgress > 0; mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); - if (mActionsView != null) { - mActionsView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); - } setClipChildren(!isFullscreen); setClipToPadding(!isFullscreen); @@ -934,5 +933,4 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mScale = scale; } } - } diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index 60cfa0cbaa..f9bb2f2b14 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -31,7 +31,6 @@ android:id="@+id/icon" android:layout_width="@dimen/task_thumbnail_icon_size" android:layout_height="@dimen/task_thumbnail_icon_size" - android:layout_gravity="top|center_horizontal" android:focusable="false" android:importantForAccessibility="no"/> \ No newline at end of file diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 45a62ab151..90d42456d2 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -66,27 +66,32 @@ Close + - Easily access your most-used apps + Get app suggestions based on your routines - Pixel suggests your favorite apps based on your routines. Tap to learn more. - - Suggested apps replace the bottom row of apps - - Your current apps will move to the last screen. To pin or block a suggested app, drag it off the bottom row. + Tap to set up + + + + Suggested apps replace the bottom row of apps + Your hotseat items will be moved up on the homescreen + Your hotseat items will be moved to the last page of your workspace + + + + Suggested apps will be found at the bottom row of your home screen + Drag one or many apps off the bottom row of home screen to see app suggestions + - Bottom row of apps moved to last screen + Bottom row of apps moved up. + Bottom row of apps moved to last page. Bottom row won\'t be replaced. Manually drag apps for predictions. - Turn On + Got it - No thanks - - Your hotseat just got smarter - - Tap here to set it up - + No thanks diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java new file mode 100644 index 0000000000..53f37c15bb --- /dev/null +++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java @@ -0,0 +1,194 @@ +/* + * 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.quickstep; + +import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.res.Resources; +import android.graphics.Point; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.Surface; + +import com.android.launcher3.ResourceUtils; +import com.android.launcher3.util.DefaultDisplay; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class OrientationTouchTransformerTest { + private static final int SIZE_WIDTH = 1080; + private static final int SIZE_HEIGHT = 2280; + private static final float DENSITY_DISPLAY_METRICS = 3.0f; + + private OrientationTouchTransformer mTouchTransformer; + + Resources mResources; + private DefaultDisplay.Info mInfo; + + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mResources = mock(Resources.class); + when(mResources.getBoolean(anyInt())).thenReturn(true); + when(mResources.getDimension(anyInt())).thenReturn(10.0f); + DisplayMetrics mockDisplayMetrics = new DisplayMetrics(); + mockDisplayMetrics.density = DENSITY_DISPLAY_METRICS; + when(mResources.getDisplayMetrics()).thenReturn(mockDisplayMetrics); + mInfo = createDisplayInfo(Surface.ROTATION_0); + mTouchTransformer = new OrientationTouchTransformer(mResources, NO_BUTTON, () -> 0); + } + + @Test + public void disabledMultipeRegions_shouldOverrideFirstRegion() { + mTouchTransformer.createOrAddTouchRegion(mInfo); + DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90); + mTouchTransformer.createOrAddTouchRegion(info2); + + float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1; + MotionEvent inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y); + mTouchTransformer.transform(inOldRegion); + assertFalse(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY())); + + // Override region + mTouchTransformer.createOrAddTouchRegion(mInfo); + inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y); + mTouchTransformer.transform(inOldRegion); + assertTrue(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY())); + } + + @Test + public void allowMultipeRegions_shouldOverrideFirstRegion() { + DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90); + mTouchTransformer.createOrAddTouchRegion(info2); + // We have to add 0 rotation second so that gets set as the current rotation, otherwise + // matrix transform will fail (tests only work in Portrait at the moment) + mTouchTransformer.enableMultipleRegions(true, mInfo); + mTouchTransformer.createOrAddTouchRegion(mInfo); + + float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1; + MotionEvent inNewRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y); + mTouchTransformer.transform(inNewRegion); + assertTrue(mTouchTransformer.touchInValidSwipeRegions(inNewRegion.getX(), inNewRegion.getY())); + } + + @Test + public void applyTransform_taskNotFrozen_notInRegion() { + mTouchTransformer.createOrAddTouchRegion(mInfo); + MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100); + mTouchTransformer.transform(outOfRegion); + assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY())); + } + + @Test + public void applyTransform_taskFrozen_noRotate_outOfRegion() { + mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.enableMultipleRegions(true, mInfo); + MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100); + mTouchTransformer.transform(outOfRegion); + assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY())); + } + + @Test + public void applyTransform_taskFrozen_noRotate_inRegion() { + mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.enableMultipleRegions(true, mInfo); + float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1; + MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y); + mTouchTransformer.transform(inRegion); + assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY())); + } + + @Test + public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() { + mTouchTransformer.createOrAddTouchRegion(mInfo); + float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1; + MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y); + mTouchTransformer.transform(inRegion); + assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY())); + } + + @Test + public void applyTransform_taskNotFrozen_90Rotate_inRegion() { + mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90)); + float y = generateTouchRegionHeight(Surface.ROTATION_90) + 1; + MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y); + mTouchTransformer.transform(inRegion); + assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY())); + } + + @Test + @Ignore("There's too much that goes into needing to mock a real motion event so the " + + "transforms in native code get applied correctly. Once that happens then maybe we can" + + " write slightly more complex unit tests") + public void applyTransform_taskNotFrozen_90Rotate_inTwoRegions() { + mTouchTransformer.createOrAddTouchRegion(mInfo); + mTouchTransformer.enableMultipleRegions(true, mInfo); + mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90)); + // Landscape point + float y1 = generateTouchRegionHeight(Surface.ROTATION_90) + 1; + MotionEvent inRegion1_down = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, y1); + MotionEvent inRegion1_up = generateMotionEvent(MotionEvent.ACTION_UP, 10, y1); + // Portrait point in landscape orientation axis + MotionEvent inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 10); + mTouchTransformer.transform(inRegion1_down); + mTouchTransformer.transform(inRegion2); + assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion1_down.getX(), inRegion1_down.getY())); + // We only process one gesture region until we see a MotionEvent.ACTION_UP + assertFalse(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY())); + + mTouchTransformer.transform(inRegion1_up); + + // Set the new region with this MotionEvent.ACTION_DOWN + inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 370); + mTouchTransformer.transform(inRegion2); + assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY())); + } + + private DefaultDisplay.Info createDisplayInfo(int rotation) { + Point p = new Point(SIZE_WIDTH, SIZE_HEIGHT); + if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { + p = new Point(SIZE_HEIGHT, SIZE_WIDTH); + } + return new DefaultDisplay.Info(0, rotation, 0, p, p, p, null); + } + + private float generateTouchRegionHeight(int rotation) { + float height = SIZE_HEIGHT; + if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { + height = SIZE_WIDTH; + } + return height - ResourceUtils.DEFAULT_NAVBAR_VALUE * DENSITY_DISPLAY_METRICS; + } + + private MotionEvent generateMotionEvent(int motionAction, float x, float y) { + return MotionEvent.obtain(0, 0, motionAction, x, y, 0); + } +} diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 84774fc22f..fb0cd17d27 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -36,11 +36,13 @@ import android.os.CancellationSignal; import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.LauncherStateManager.StateHandler; +import com.android.launcher3.accessibility.SystemActions; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.WellbeingModel; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.proxy.ProxyActivityStarter; import com.android.launcher3.proxy.StartActivityParams; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.uioverrides.BackButtonAlphaHandler; import com.android.launcher3.uioverrides.RecentsViewStateController; import com.android.launcher3.util.UiThreadHelper; @@ -51,6 +53,7 @@ import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.util.RemoteFadeOutAnimationListener; import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.ActivityManagerWrapper; import java.util.stream.Stream; @@ -61,6 +64,8 @@ import java.util.stream.Stream; public abstract class BaseQuickstepLauncher extends Launcher implements NavigationModeChangeListener { + protected SystemActions mSystemActions; + /** * Reusable command for applying the back button alpha on the background thread. */ @@ -73,6 +78,7 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mSystemActions = new SystemActions(this); SysUINavigationMode.Mode mode = SysUINavigationMode.INSTANCE.get(this) .addModeChangeListener(this); @@ -130,6 +136,12 @@ public abstract class BaseQuickstepLauncher extends Launcher getRotationHelper().setRotationHadDifferentUI(newMode != Mode.NO_BUTTON); } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + mSystemActions.onActivityResult(requestCode); + } + @Override public void onEnterAnimationComplete() { super.onEnterAnimationComplete(); @@ -193,6 +205,15 @@ public abstract class BaseQuickstepLauncher extends Launcher // removes the task itself. startActivity(ProxyActivityStarter.getLaunchIntent(this, null)); } + + // Register all system actions once they are available + mSystemActions.register(); + } + + @Override + protected void onPause() { + super.onPause(); + mSystemActions.unregister(); } @Override @@ -201,7 +222,10 @@ public abstract class BaseQuickstepLauncher extends Launcher if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) { // Overview is above all other launcher elements, including qsb, so move it to the top. - getOverviewPanelContainer().bringToFront(); + getOverviewPanel().bringToFront(); + if (getActionsView() != null) { + getActionsView().bringToFront(); + } } } @@ -217,9 +241,10 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() { if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) { - float offscreenTranslationX = getDeviceProfile().widthPx - - getOverviewPanel().getPaddingStart(); - return new ScaleAndTranslation(1f, offscreenTranslationX, 0f); + PagedOrientationHandler layoutVertical = + ((RecentsView)getOverviewPanel()).getPagedViewOrientedState().getOrientationHandler(); + return layoutVertical.getScaleAndTranslation(getDeviceProfile(), + getOverviewPanel()); } return super.getOverviewScaleAndTranslationForNormalState(); } diff --git a/quickstep/src/com/android/launcher3/accessibility/SystemActions.java b/quickstep/src/com/android/launcher3/accessibility/SystemActions.java new file mode 100644 index 0000000000..669877f5b3 --- /dev/null +++ b/quickstep/src/com/android/launcher3/accessibility/SystemActions.java @@ -0,0 +1,90 @@ +/* + * 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.accessibility; + +import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.LauncherState.NORMAL; + +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.drawable.Icon; +import android.view.accessibility.AccessibilityManager; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; +import com.android.launcher3.R; + +/** + * Manages the launcher system actions presented to accessibility services. + */ +public class SystemActions { + + /** + * System Action ID to show all apps. This ID should follow the ones in + * com.android.systemui.accessibility.SystemActions. + */ + private static final int SYSTEM_ACTION_ID_ALL_APPS = 100; + + private Launcher mLauncher; + private AccessibilityManager mAccessibilityManager; + private RemoteAction mAllAppsAction; + private boolean mRegistered; + + public SystemActions(Launcher launcher) { + mLauncher = launcher; + mAccessibilityManager = (AccessibilityManager) launcher.getSystemService( + Context.ACCESSIBILITY_SERVICE); + mAllAppsAction = new RemoteAction( + Icon.createWithResource(launcher, R.drawable.ic_apps), + launcher.getString(R.string.all_apps_label), + launcher.getString(R.string.all_apps_label), + launcher.createPendingResult(SYSTEM_ACTION_ID_ALL_APPS, new Intent(), + 0 /* flags */)); + } + + public void register() { + if (mRegistered) { + return; + } + mAccessibilityManager.registerSystemAction(mAllAppsAction, SYSTEM_ACTION_ID_ALL_APPS); + mRegistered = true; + } + + public void unregister() { + if (!mRegistered) { + return; + } + mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS); + mRegistered = false; + } + + public void onActivityResult(int requestCode) { + if (requestCode == SYSTEM_ACTION_ID_ALL_APPS) { + showAllApps(); + } + } + + private void showAllApps() { + LauncherStateManager stateManager = mLauncher.getStateManager(); + stateManager.goToState(NORMAL); + stateManager.goToState(ALL_APPS); + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java index 174e49b8d2..7481445600 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java @@ -52,10 +52,12 @@ public abstract class BaseRecentsViewStateController implements StateHandler { protected final T mRecentsView; protected final Launcher mLauncher; + protected final View mActionsView; public BaseRecentsViewStateController(@NonNull Launcher launcher) { mLauncher = launcher; mRecentsView = launcher.getOverviewPanel(); + mActionsView = launcher.getActionsView(); } @Override @@ -72,6 +74,10 @@ public abstract class BaseRecentsViewStateController getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0); OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim(); SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher)); + if (mActionsView != null) { + mActionsView.setTranslationX(translationX); + mActionsView.setAlpha(state.overviewUi ? 1f : 0); + } } @Override @@ -118,6 +124,11 @@ public abstract class BaseRecentsViewStateController OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim(); setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher), builder.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR)); + if (mActionsView != null) { + setter.setFloat(mActionsView, View.TRANSLATION_X, translationX, translateXInterpolator); + setter.setFloat(mActionsView, View.ALPHA, toState.overviewUi ? 1 : 0, + builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT)); + } } /** diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index fd55e077fd..1d71fe226b 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; +import android.util.Pair; import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; @@ -31,6 +32,7 @@ import androidx.annotation.UiThread; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.ShelfPeekAnim; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -49,6 +51,15 @@ public interface BaseActivityInterface { int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect); + /** + * @return The progress of the swipe where we start resisting the user, where 0 is fullscreen + * and 1 is recents. These values should probably be greater than 1 to let the user swipe past + * recents before we start resisting them. + */ + default Pair getSwipeUpPullbackStartAndMaxProgress() { + return new Pair<>(1.4f, 1.8f); + } + void onSwipeUpToRecentsComplete(); default void onSwipeUpToHomeComplete() { } @@ -148,13 +159,18 @@ public interface BaseActivityInterface { // No-op } - static RectF getDefaultWindowTargetRect(DeviceProfile dp) { + static RectF getDefaultWindowTargetRect(PagedOrientationHandler orientationHandler, + DeviceProfile dp) { final int halfIconSize = dp.iconSizePx / 2; - final float targetCenterX = dp.availableWidthPx / 2f; - final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx; + float primaryDimension = orientationHandler + .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx); + float secondaryDimension = orientationHandler + .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx); + final float targetX = primaryDimension / 2f; + final float targetY = secondaryDimension - dp.hotseatBarSizePx; // Fallback to animate to center of screen. - return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize, - targetCenterX + halfIconSize, targetCenterY + halfIconSize); + return new RectF(targetX - halfIconSize, targetY - halfIconSize, + targetX + halfIconSize, targetY + halfIconSize); } } diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index ae0886b913..501c6f0b74 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -19,10 +19,12 @@ import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; import android.app.ActivityManager; import android.content.Intent; + import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.systemui.shared.recents.model.ThumbnailData; + +import java.io.PrintWriter; import java.util.ArrayList; /** @@ -281,4 +283,13 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED); mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); } + + public void dump(PrintWriter pw) { + pw.println("GestureState:"); + pw.println(" gestureID=" + mGestureId); + pw.println(" runningTask=" + mRunningTask); + pw.println(" endTarget=" + mEndTarget); + pw.println(" finishingRecentsAnimationTaskId=" + mFinishingRecentsAnimationTaskId); + pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning()); + } } diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java new file mode 100644 index 0000000000..92eb036fbc --- /dev/null +++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java @@ -0,0 +1,341 @@ +/* + * 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.quickstep; + +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_POINTER_DOWN; +import static android.view.MotionEvent.ACTION_UP; + +import android.content.res.Resources; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.RectF; +import android.util.Log; +import android.util.SparseArray; +import android.view.MotionEvent; +import android.view.Surface; + +import com.android.launcher3.R; +import com.android.launcher3.ResourceUtils; +import com.android.launcher3.states.RotationHelper; +import com.android.launcher3.util.DefaultDisplay; + +import java.io.PrintWriter; + +/** + * Maintains state for supporting nav bars and tracking their gestures in multiple orientations. + * See {@link OrientationRectF#applyTransform(MotionEvent, boolean)} for transformation of + * MotionEvents from one orientation's coordinate space to another's. + * + * This class only supports single touch/pointer gesture tracking for touches started in a supported + * nav bar region. + */ +class OrientationTouchTransformer { + + private static final String TAG = "OrientationTouchTransformer"; + private static final boolean DEBUG = false; + private static final int MAX_ORIENTATIONS = 4; + + private SparseArray mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS); + private final RectF mAssistantLeftRegion = new RectF(); + private final RectF mAssistantRightRegion = new RectF(); + private int mCurrentRotation; + private boolean mEnableMultipleRegions; + private Resources mResources; + private OrientationRectF mLastRectTouched; + private SysUINavigationMode.Mode mMode; + private QuickStepContractInfo mContractInfo; + private int mQuickStepStartingRotation = -1; + + /** For testability */ + interface QuickStepContractInfo { + float getWindowCornerRadius(); + } + + OrientationTouchTransformer(Resources resources, SysUINavigationMode.Mode mode, + QuickStepContractInfo contractInfo) { + mResources = resources; + mMode = mode; + mContractInfo = contractInfo; + } + + void setNavigationMode(SysUINavigationMode.Mode newMode) { + this.mMode = newMode; + } + + /** + * Sets the current nav bar region to listen to events for as determined by + * {@param info}. If multiple nav bar regions are enabled, then this region will be added + * alongside other regions. + * Ok to call multiple times + * + * @see #enableMultipleRegions(boolean, DefaultDisplay.Info) + */ + void createOrAddTouchRegion(DefaultDisplay.Info info) { + mCurrentRotation = info.rotation; + if (mQuickStepStartingRotation > -1 && mCurrentRotation == mQuickStepStartingRotation) { + // Ignore nav bars in other rotations except for the one we started out in + resetSwipeRegions(info); + return; + } + + OrientationRectF region = mSwipeTouchRegions.get(mCurrentRotation); + if (region != null) { + return; + } + + if (mEnableMultipleRegions) { + mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(info)); + } else { + resetSwipeRegions(info); + } + } + + /** + * Call when we want to start tracking nav bar touch regions in multiple orientations. + * ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done. + * + * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions + * @param info The current displayInfo + */ + void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) { + mEnableMultipleRegions = enableMultipleRegions; + if (!enableMultipleRegions) { + mQuickStepStartingRotation = -1; + resetSwipeRegions(info); + } else { + if (mQuickStepStartingRotation < 0) { + mQuickStepStartingRotation = mLastRectTouched.mRotation; + } + } + } + + /** + * Only saves the swipe region represented by {@param region}, clears the + * rest from {@link #mSwipeTouchRegions} + * To be called whenever we want to stop tracking more than one swipe region. + * Ok to call multiple times. + */ + private void resetSwipeRegions(DefaultDisplay.Info region) { + if (DEBUG) { + Log.d(TAG, "clearing all regions except rotation: " + mCurrentRotation); + } + + mCurrentRotation = region.rotation; + OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentRotation); + mSwipeTouchRegions.clear(); + mSwipeTouchRegions.put(mCurrentRotation, + regionToKeep != null ? regionToKeep : createRegionForDisplay(region)); + } + + private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) { + if (DEBUG) { + Log.d(TAG, "creating rotation region for: " + mCurrentRotation); + } + + Point size = display.realSize; + int rotation = display.rotation; + OrientationRectF orientationRectF = + new OrientationRectF(0, 0, size.x, size.y, rotation); + if (mMode == SysUINavigationMode.Mode.NO_BUTTON) { + int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); + orientationRectF.top = orientationRectF.bottom - touchHeight; + + final int assistantWidth = mResources + .getDimensionPixelSize(R.dimen.gestures_assistant_width); + final float assistantHeight = Math.max(touchHeight, + mContractInfo.getWindowCornerRadius()); + mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom; + mAssistantLeftRegion.top = mAssistantRightRegion.top = + orientationRectF.bottom - assistantHeight; + + mAssistantLeftRegion.left = 0; + mAssistantLeftRegion.right = assistantWidth; + + mAssistantRightRegion.right = orientationRectF.right; + mAssistantRightRegion.left = orientationRectF.right - assistantWidth; + } else { + mAssistantLeftRegion.setEmpty(); + mAssistantRightRegion.setEmpty(); + switch (rotation) { + case Surface.ROTATION_90: + orientationRectF.left = orientationRectF.right + - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE); + break; + case Surface.ROTATION_270: + orientationRectF.right = orientationRectF.left + + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE); + break; + default: + orientationRectF.top = orientationRectF.bottom + - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); + } + } + + return orientationRectF; + } + + boolean touchInAssistantRegion(MotionEvent ev) { + return mAssistantLeftRegion.contains(ev.getX(), ev.getY()) + || mAssistantRightRegion.contains(ev.getX(), ev.getY()); + + } + + private int getNavbarSize(String resName) { + return ResourceUtils.getNavbarSize(resName, mResources); + } + + boolean touchInValidSwipeRegions(float x, float y) { + if (mLastRectTouched != null) { + return mLastRectTouched.contains(x, y); + } + return false; + } + + int getCurrentActiveRotation() { + if (mLastRectTouched == null) { + return 0; + } else { + return mLastRectTouched.mRotation; + } + } + + public void transform(MotionEvent event) { + int eventAction = event.getActionMasked(); + switch (eventAction) { + case ACTION_MOVE: { + if (mLastRectTouched == null) { + return; + } + mLastRectTouched.applyTransform(event, true); + break; + } + case ACTION_CANCEL: + case ACTION_UP: { + if (mLastRectTouched == null) { + return; + } + mLastRectTouched.applyTransform(event, true); + mLastRectTouched = null; + break; + } + case ACTION_POINTER_DOWN: + case ACTION_DOWN: { + if (mLastRectTouched != null) { + return; + } + + for (int i = 0; i < MAX_ORIENTATIONS; i++) { + OrientationRectF rect = mSwipeTouchRegions.get(i); + if (rect == null) { + continue; + } + if (rect.applyTransform(event, false)) { + mLastRectTouched = rect; + if (DEBUG) { + Log.d(TAG, "set active region: " + rect); + } + return; + } + } + break; + } + } + } + + public void dump(PrintWriter pw) { + pw.println("OrientationTouchTransformerState: "); + pw.println(" currentActiveRotation=" + getCurrentActiveRotation()); + pw.println(" lastTouchedRegion=" + mLastRectTouched); + pw.println(" multipleRegionsEnabled=" + mEnableMultipleRegions); + StringBuilder regions = new StringBuilder(" currentTouchableRotations="); + for(int i = 0; i < mSwipeTouchRegions.size(); i++) { + OrientationRectF rectF = mSwipeTouchRegions.get(mSwipeTouchRegions.keyAt(i)); + regions.append(rectF.mRotation).append(" "); + } + pw.println(regions.toString()); + } + + private class OrientationRectF extends RectF { + + /** + * Delta to subtract width and height by because if we report the translated touch + * bounds as the width and height, calling {@link RectF#contains(float, float)} will + * be false + */ + private float maxDelta = 0.001f; + + private int mRotation; + private float mHeight; + private float mWidth; + + OrientationRectF(float left, float top, float right, float bottom, int rotation) { + super(left, top, right, bottom); + this.mRotation = rotation; + mHeight = bottom - maxDelta; + mWidth = right - maxDelta; + } + + @Override + public String toString() { + String s = super.toString(); + s += " rotation: " + mRotation; + return s; + } + + boolean applyTransform(MotionEvent event, boolean forceTransform) { + // TODO(b/149658423): See if we can use RotationHelper.getRotationMatrix here + MotionEvent tmp = MotionEvent.obtain(event); + Matrix outMatrix = new Matrix(); + int delta = RotationHelper.deltaRotation(mCurrentRotation, mRotation); + switch (delta) { + case Surface.ROTATION_0: + outMatrix.reset(); + break; + case Surface.ROTATION_90: + outMatrix.setRotate(270); + outMatrix.postTranslate(0, mHeight); + break; + case Surface.ROTATION_180: + outMatrix.setRotate(180); + outMatrix.postTranslate(mHeight, mWidth); + break; + case Surface.ROTATION_270: + outMatrix.setRotate(90); + outMatrix.postTranslate(mWidth, 0); + break; + } + + tmp.transform(outMatrix); + if (DEBUG) { + Log.d(TAG, "original: " + event.getX() + ", " + event.getY() + + " new: " + tmp.getX() + ", " + tmp.getY() + + " rect: " + this + " forceTransform: " + forceTransform + + " contains: " + contains(tmp.getX(), tmp.getY())); + } + + if (forceTransform || contains(tmp.getX(), tmp.getY())) { + event.transform(outMatrix); + tmp.recycle(); + return true; + } + return false; + } + } +} diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java index 85ef4c6a91..866836ee72 100644 --- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java +++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java @@ -33,6 +33,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.util.SparseIntArray; +import com.android.launcher3.util.SimpleBroadcastReceiver; import com.android.systemui.shared.system.PackageManagerWrapper; import java.io.PrintWriter; @@ -44,18 +45,11 @@ import java.util.Objects; * and provide callers the relevant classes. */ public final class OverviewComponentObserver { - private final BroadcastReceiver mUserPreferenceChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - updateOverviewTargets(); - } - }; - private final BroadcastReceiver mOtherHomeAppUpdateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - updateOverviewTargets(); - } - }; + private final BroadcastReceiver mUserPreferenceChangeReceiver = + new SimpleBroadcastReceiver(this::updateOverviewTargets); + private final BroadcastReceiver mOtherHomeAppUpdateReceiver = + new SimpleBroadcastReceiver(this::updateOverviewTargets); + private final Context mContext; private final RecentsAnimationDeviceState mDeviceState; private final Intent mCurrentHomeIntent; @@ -106,6 +100,10 @@ public final class OverviewComponentObserver { } } + private void updateOverviewTargets(Intent unused) { + updateOverviewTargets(); + } + /** * Update overview intent and {@link BaseActivityInterface} based off the current launcher home * component. @@ -131,11 +129,8 @@ public final class OverviewComponentObserver { mOverviewIntent = mMyHomeIntent; mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent()); - if (mUpdateRegisteredPackage != null) { - // Remove any update listener as we don't care about other packages. - mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver); - mUpdateRegisteredPackage = null; - } + // Remove any update listener as we don't care about other packages. + unregisterOtherHomeAppUpdateReceiver(); } else { // The default home app is a different launcher. Use the fallback Overview instead. @@ -149,13 +144,9 @@ public final class OverviewComponentObserver { // Listen for package updates of this app (and remove any previously attached // package listener). if (defaultHome == null) { - if (mUpdateRegisteredPackage != null) { - mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver); - } + unregisterOtherHomeAppUpdateReceiver(); } else if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) { - if (mUpdateRegisteredPackage != null) { - mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver); - } + unregisterOtherHomeAppUpdateReceiver(); mUpdateRegisteredPackage = defaultHome.getPackageName(); mContext.registerReceiver(mOtherHomeAppUpdateReceiver, getPackageFilter( @@ -170,7 +161,10 @@ public final class OverviewComponentObserver { */ public void onDestroy() { mContext.unregisterReceiver(mUserPreferenceChangeReceiver); + unregisterOtherHomeAppUpdateReceiver(); + } + private void unregisterOtherHomeAppUpdateReceiver() { if (mUpdateRegisteredPackage != null) { mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver); mUpdateRegisteredPackage = null; diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index 21a4918b1b..8dd4aa4cab 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -39,7 +39,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; /** - * Wrapper around RecentsAnimationController to help with some synchronization + * Wrapper around RecentsAnimationControllerCompat to help with some synchronization */ public class RecentsAnimationController { diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index abe15927d4..d845650637 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -17,8 +17,6 @@ package com.android.quickstep; import static android.content.Intent.ACTION_USER_UNLOCKED; -import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE; -import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; @@ -40,20 +38,17 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.RectF; import android.graphics.Region; import android.os.Process; import android.os.UserManager; import android.text.TextUtils; import android.util.Log; import android.view.MotionEvent; -import android.view.Surface; import androidx.annotation.BinderThread; +import com.android.launcher3.PagedView; import com.android.launcher3.R; -import com.android.launcher3.ResourceUtils; import com.android.launcher3.Utilities; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.DefaultDisplay; @@ -63,6 +58,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat; +import com.android.systemui.shared.system.TaskStackChangeListener; import java.io.PrintWriter; import java.util.ArrayList; @@ -80,6 +76,7 @@ public class RecentsAnimationDeviceState implements private final SysUINavigationMode mSysUiNavMode; private final DefaultDisplay mDefaultDisplay; private final int mDisplayId; + private int mDisplayRotation; private final ArrayList mOnDestroyActions = new ArrayList<>(); @@ -87,10 +84,7 @@ public class RecentsAnimationDeviceState implements private SysUINavigationMode.Mode mMode = THREE_BUTTONS; private NavBarPosition mNavBarPosition; - private final RectF mSwipeUpTouchRegion = new RectF(); private final Region mDeferredGestureRegion = new Region(); - private final RectF mAssistantLeftRegion = new RectF(); - private final RectF mAssistantRightRegion = new RectF(); private boolean mAssistantAvailable; private float mAssistantVisibility; @@ -106,10 +100,13 @@ public class RecentsAnimationDeviceState implements } }; + private OrientationTouchTransformer mOrientationTouchTransformer; + private Region mExclusionRegion; private SystemGestureExclusionListenerCompat mExclusionListener; private final List mGestureBlockedActivities; + private TaskStackChangeListener mFrozenTaskListener; public RecentsAnimationDeviceState(Context context) { final ContentResolver resolver = context.getContentResolver(); @@ -139,6 +136,8 @@ public class RecentsAnimationDeviceState implements }; runOnDestroy(mExclusionListener::unregister); + setupOrientationSwipeHandler(context); + // Register for navigation mode changes onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this)); runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this)); @@ -160,6 +159,26 @@ public class RecentsAnimationDeviceState implements } } + private void setupOrientationSwipeHandler(Context context) { + final Resources resources = context.getResources(); + mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode, + () -> QuickStepContract.getWindowCornerRadius(resources)); + + if (!PagedView.sFlagForcedRotation) { + return; + } + + mFrozenTaskListener = new TaskStackChangeListener() { + @Override + public void onRecentTaskListFrozenChanged(boolean frozen) { + mOrientationTouchTransformer.enableMultipleRegions(frozen, mDefaultDisplay.getInfo()); + } + }; + ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener); + runOnDestroy(() -> ActivityManagerWrapper.getInstance() + .unregisterTaskStackListener(mFrozenTaskListener)); + } + private void runOnDestroy(Runnable action) { mOnDestroyActions.add(action); } @@ -198,7 +217,10 @@ public class RecentsAnimationDeviceState implements mExclusionListener.unregister(); } mMode = newMode; + mNavBarPosition = new NavBarPosition(mMode, mDefaultDisplay.getInfo()); + + mOrientationTouchTransformer.setNavigationMode(mMode); } @Override @@ -207,8 +229,10 @@ public class RecentsAnimationDeviceState implements return; } + mDisplayRotation = info.rotation; mNavBarPosition = new NavBarPosition(mMode, info); updateGestureTouchRegions(); + mOrientationTouchTransformer.createOrAddTouchRegion(info); } /** @@ -380,50 +404,14 @@ public class RecentsAnimationDeviceState implements return; } - Resources res = mContext.getResources(); - DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo(); - Point realSize = new Point(displayInfo.realSize); - mSwipeUpTouchRegion.set(0, 0, realSize.x, realSize.y); - if (mMode == NO_BUTTON) { - int touchHeight = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE, res); - mSwipeUpTouchRegion.top = mSwipeUpTouchRegion.bottom - touchHeight; - - final int assistantWidth = res.getDimensionPixelSize(R.dimen.gestures_assistant_width); - final float assistantHeight = Math.max(touchHeight, - QuickStepContract.getWindowCornerRadius(res)); - mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mSwipeUpTouchRegion.bottom; - mAssistantLeftRegion.top = mAssistantRightRegion.top = - mSwipeUpTouchRegion.bottom - assistantHeight; - - mAssistantLeftRegion.left = 0; - mAssistantLeftRegion.right = assistantWidth; - - mAssistantRightRegion.right = mSwipeUpTouchRegion.right; - mAssistantRightRegion.left = mSwipeUpTouchRegion.right - assistantWidth; - } else { - mAssistantLeftRegion.setEmpty(); - mAssistantRightRegion.setEmpty(); - switch (displayInfo.rotation) { - case Surface.ROTATION_90: - mSwipeUpTouchRegion.left = mSwipeUpTouchRegion.right - - ResourceUtils.getNavbarSize(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, res); - break; - case Surface.ROTATION_270: - mSwipeUpTouchRegion.right = mSwipeUpTouchRegion.left - + ResourceUtils.getNavbarSize(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, res); - break; - default: - mSwipeUpTouchRegion.top = mSwipeUpTouchRegion.bottom - - ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE, res); - } - } + mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo()); } /** * @return whether the coordinates of the {@param event} is in the swipe up gesture region. */ public boolean isInSwipeUpTouchRegion(MotionEvent event) { - return mSwipeUpTouchRegion.contains(event.getX(), event.getY()); + return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY()); } /** @@ -431,7 +419,8 @@ public class RecentsAnimationDeviceState implements * is in the swipe up gesture region. */ public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) { - return mSwipeUpTouchRegion.contains(event.getX(pointerIndex), event.getY(pointerIndex)); + return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex), + event.getY(pointerIndex)); } /** @@ -490,11 +479,34 @@ public class RecentsAnimationDeviceState implements public boolean canTriggerAssistantAction(MotionEvent ev) { return mAssistantAvailable && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags) - && (mAssistantLeftRegion.contains(ev.getX(), ev.getY()) - || mAssistantRightRegion.contains(ev.getX(), ev.getY())) + && mOrientationTouchTransformer.touchInAssistantRegion(ev) && !isLockToAppActive(); } + /** + * *May* apply a transform on the motion event if it lies in the nav bar region for another + * orientation that is currently being tracked as a part of quickstep + */ + public void setOrientationTransformIfNeeded(MotionEvent event) { + // negative coordinates bug b/143901881 + if (event.getX() < 0 || event.getY() < 0) { + event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY())); + } + mOrientationTouchTransformer.transform(event); + } + + public void enableMultipleRegions(boolean enable) { + mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo()); + } + + public int getCurrentActiveRotation() { + return mOrientationTouchTransformer.getCurrentActiveRotation(); + } + + public int getDisplayRotation() { + return mDisplayRotation; + } + public void dump(PrintWriter pw) { pw.println("DeviceState:"); pw.println(" canStartSystemGesture=" + canStartSystemGesture()); @@ -504,5 +516,8 @@ public class RecentsAnimationDeviceState implements pw.println(" assistantAvailable=" + mAssistantAvailable); pw.println(" assistantDisabled=" + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)); + pw.println(" currentActiveRotation=" + getCurrentActiveRotation()); + pw.println(" displayRotation=" + getDisplayRotation()); + mOrientationTouchTransformer.dump(pw); } } diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index eb60601c44..0210a814cf 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -333,4 +333,15 @@ public class SystemUiProxy implements ISystemUiProxy { } } } + + public void onQuickSwitchToNewTask() { + //TODO(b/150250451) add back in after big CL goes through +// if (mSystemUiProxy != null) { +// try { +// mSystemUiProxy.onQuickSwitchToNewTask(); +// } catch (RemoteException e) { +// Log.w(TAG, "Failed call onQuickstepStarted", e); +// } +// } + } } diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index 1d8a79f683..ba99016561 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -33,6 +33,9 @@ import com.android.quickstep.SysUINavigationMode; import java.lang.annotation.Retention; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; +import static java.lang.annotation.RetentionPolicy.SOURCE; + public class LayoutUtils { private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1; diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java index 3e84a76695..ccfa3fc0fb 100644 --- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java +++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java @@ -11,6 +11,7 @@ import static org.junit.Assert.assertTrue; import android.app.PendingIntent; import android.app.usage.UsageStatsManager; import android.content.Intent; +import android.os.Build; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -33,6 +34,9 @@ public class DigitalWellBeingToastTest extends AbstractQuickStepTest { @Test public void testToast() throws Exception { + // b/150303529 + if (Build.MODEL.contains("Cuttlefish")) return; + startAppFast(CALCULATOR_PACKAGE); final UsageStatsManager usageStatsManager = diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index 03d7c3e31a..75fcfe2f19 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -17,8 +17,6 @@ package com.android.quickstep; import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; -import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; -import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -41,7 +39,6 @@ import com.android.launcher3.tapl.Overview; import com.android.launcher3.tapl.OverviewTask; import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.ui.TaplTestsLauncher3; -import com.android.launcher3.util.rule.TestStabilityRule; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; import com.android.quickstep.views.RecentsView; @@ -254,8 +251,6 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Test @NavigationModeSwitch @PortraitLandscape - // b/143285809 Remove @Stability on 02/27/20 if the test doesn't flake. - @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT) public void testQuickSwitchFromApp() throws Exception { startTestActivity(2); startTestActivity(3); diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml index 6c66897a45..196eb0f1cf 100644 --- a/res/layout/launcher.xml +++ b/res/layout/launcher.xml @@ -44,8 +44,13 @@ layout="@layout/hotseat" /> + android:id="@+id/overview_panel" + layout="@layout/overview_panel" + android:visibility="gone" /> + + diff --git a/res/layout/overview_actions_holder.xml b/res/layout/overview_actions_holder.xml new file mode 100644 index 0000000000..5946bf67ec --- /dev/null +++ b/res/layout/overview_actions_holder.xml @@ -0,0 +1,20 @@ + + + diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml index 7fff711190..2637f038c5 100644 --- a/res/layout/overview_panel.xml +++ b/res/layout/overview_panel.xml @@ -14,9 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - \ No newline at end of file + \ No newline at end of file diff --git a/res/values/config.xml b/res/values/config.xml index 8aff6da7dc..df0f2330f0 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -121,5 +121,57 @@ true - + 0.75 + 600 + + 0.5 + 1500 + + 0.5 + 1500 + + 0.75 + 200 + + 0.75 + 200 + + 1.5 + 0.8 + 200 + + 0.7 + 150 + + + 18dp + 10dp + -60dp + + + @dimen/all_apps_spring_damping_ratio + @dimen/all_apps_spring_stiffness + + @dimen/dismiss_task_trans_y_damping_ratio + @dimen/dismiss_task_trans_y_stiffness + + @dimen/dismiss_task_trans_x_damping_ratio + @dimen/dismiss_task_trans_x_stiffness + + @dimen/horizontal_spring_damping_ratio + @dimen/horizontal_spring_stiffness + + @dimen/swipe_up_rect_scale_damping_ratio + @dimen/swipe_up_rect_scale_stiffness + + @dimen/swipe_up_rect_xy_fling_friction + @dimen/swipe_up_rect_xy_damping_ratio + @dimen/swipe_up_rect_xy_stiffness + + @dimen/staggered_damping_ratio + @dimen/staggered_stiffness + + @dimen/swipe_up_fling_min_visible_change + @dimen/swipe_up_y_overshoot + diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 8718820bd2..e3eb38792a 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -2783,6 +2783,26 @@ public class CellLayout extends ViewGroup implements Transposable { return false; } + /** + * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig + */ + public boolean makeSpaceForHotseatMigration(boolean commitConfig) { + if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) return false; + int[] cellPoint = new int[2]; + int[] directionVector = new int[]{0, -1}; + cellToPoint(0, mCountY, cellPoint); + ItemConfiguration configuration = new ItemConfiguration(); + if (findReorderSolution(cellPoint[0], cellPoint[1], mCountX, 1, mCountX, 1, + directionVector, null, false, configuration).isSolution) { + if (commitConfig) { + copySolutionToTempState(configuration, null); + commitTempPlacement(); + } + return true; + } + return false; + } + public boolean isRegionVacant(int x, int y, int spanX, int spanY) { return mOccupied.isRegionVacant(x, y, spanX, spanY); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 06d091426c..20ebc7a27d 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -80,7 +80,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.animation.OvershootInterpolator; -import android.widget.FrameLayout; import android.widget.Toast; import androidx.annotation.Nullable; @@ -274,7 +273,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // UI and state for the overview panel private View mOverviewPanel; - private FrameLayout mOverviewPanelContainer; + private View mActionsView; @Thunk boolean mWorkspaceLoading = true; @@ -834,6 +833,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } } + mDragLayer.clearAnimatedView(); } @@ -1162,8 +1162,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mFocusHandler = mDragLayer.getFocusIndicatorHelper(); mWorkspace = mDragLayer.findViewById(R.id.workspace); mWorkspace.initParentViews(mDragLayer); - mOverviewPanel = findViewById(R.id.overview_panel_recents); - mOverviewPanelContainer = findViewById(R.id.overview_panel_container); + mOverviewPanel = findViewById(R.id.overview_panel); + mActionsView = findViewById(R.id.overview_actions_view); mHotseat = findViewById(R.id.hotseat); mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN @@ -1363,7 +1363,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, protected void onUiChangedWhileSleeping() { } - public void updateNotificationDots(Predicate updatedDots) { + private void updateNotificationDots(Predicate updatedDots) { mWorkspace.updateNotificationDots(updatedDots); mAppsView.getAppsStore().updateNotificationDots(updatedDots); } @@ -1411,8 +1411,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return (T) mOverviewPanel; } - public FrameLayout getOverviewPanelContainer() { - return mOverviewPanelContainer; + public View getActionsView() { + return mActionsView; } public DropTargetBar getDropTargetBar() { @@ -2327,6 +2327,13 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED; getModelWriter().updateItemInDatabase(item); } + else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) + && appWidgetInfo.configure != null) { + if (mAppWidgetManager.isAppWidgetRestored(item.appWidgetId)) { + item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED; + getModelWriter().updateItemInDatabase(item); + } + } } if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { @@ -2340,6 +2347,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, item.minSpanX = appWidgetInfo.minSpanX; item.minSpanY = appWidgetInfo.minSpanY; view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo); + } else if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) + && appWidgetInfo != null) { + mAppWidgetHost.addPendingView(item.appWidgetId, + new PendingAppWidgetHostView(this, item, mIconCache, false)); + view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo); } else { view = new PendingAppWidgetHostView(this, item, mIconCache, false); } diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java index 4e29a95fb2..9921f76f0c 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/LauncherAppWidgetHost.java @@ -32,6 +32,7 @@ import android.widget.Toast; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.widget.DeferredAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetHostView; +import com.android.launcher3.widget.PendingAppWidgetHostView; import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.ArrayList; @@ -53,12 +54,14 @@ public class LauncherAppWidgetHost extends AppWidgetHost { private final ArrayList mProviderChangeListeners = new ArrayList<>(); private final SparseArray mViews = new SparseArray<>(); + private final SparseArray mPendingViews = new SparseArray<>(); private final Context mContext; private int mFlags = FLAG_RESUMED; private IntConsumer mAppWidgetRemovedCallback = null; + public LauncherAppWidgetHost(Context context) { this(context, null); } @@ -73,7 +76,13 @@ public class LauncherAppWidgetHost extends AppWidgetHost { @Override protected LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { - LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context); + final LauncherAppWidgetHostView view; + if (mPendingViews.get(appWidgetId) != null) { + view = mPendingViews.get(appWidgetId); + mPendingViews.remove(appWidgetId); + } else { + view = new LauncherAppWidgetHostView(context); + } mViews.put(appWidgetId, view); return view; } @@ -189,6 +198,10 @@ public class LauncherAppWidgetHost extends AppWidgetHost { } } + void addPendingView(int appWidgetId, PendingAppWidgetHostView view) { + mPendingViews.put(appWidgetId, view); + } + public AppWidgetHostView createView(Context context, int appWidgetId, LauncherAppWidgetProviderInfo appWidget) { if (appWidget.isCustomWidget()) { @@ -238,8 +251,8 @@ public class LauncherAppWidgetHost extends AppWidgetHost { /** * Called on an appWidget is removed for a widgetId - * @param appWidgetId - * TODO: make this override when SDK is updated + * + * @param appWidgetId TODO: make this override when SDK is updated */ public void onAppWidgetRemoved(int appWidgetId) { if (mAppWidgetRemovedCallback == null) { diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 01893e92b1..a6180a6360 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -16,11 +16,6 @@ package com.android.launcher3; -import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled; -import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; -import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; -import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR; - import android.animation.LayoutTransition; import android.animation.TimeInterpolator; import android.annotation.SuppressLint; @@ -35,6 +30,7 @@ import android.util.Log; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.Surface; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; @@ -49,13 +45,26 @@ import android.widget.ScrollView; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.model.PagedViewOrientedState; import com.android.launcher3.pageindicators.PageIndicator; +import com.android.launcher3.states.RotationHelper; +import com.android.launcher3.touch.PortraitPagedViewHandler; import com.android.launcher3.touch.OverScroll; +import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds; import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.Thunk; import java.util.ArrayList; +import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled; +import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; +import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; +import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR; +import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; +import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY; +import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO; + /** * An abstraction of the original Workspace which supports browsing through a * sequential list of "pages" @@ -64,6 +73,8 @@ public abstract class PagedView extends ViewGrou private static final String TAG = "PagedView"; private static final boolean DEBUG = false; + public static boolean sFlagForcedRotation = false; + public static final int INVALID_PAGE = -1; protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE; @@ -97,8 +108,8 @@ public abstract class PagedView extends ViewGrou @ViewDebug.ExportedProperty(category = "launcher") protected int mNextPage = INVALID_PAGE; - protected int mMinScrollX; - protected int mMaxScrollX; + protected int mMaxScroll; + protected int mMinScroll; protected OverScroller mScroller; private Interpolator mDefaultInterpolator; private VelocityTracker mVelocityTracker; @@ -106,9 +117,12 @@ public abstract class PagedView extends ViewGrou private float mDownMotionX; private float mDownMotionY; - private float mLastMotionX; - private float mLastMotionXRemainder; - private float mTotalMotionX; + private float mDownMotionPrimary; + private float mLastMotion; + private float mLastMotionRemainder; + private float mTotalMotion; + protected PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler(); + protected final PagedViewOrientedState mOrientationState = new PagedViewOrientedState(); protected int[] mPageScrolls; private boolean mIsBeingDragged; @@ -123,11 +137,14 @@ public abstract class PagedView extends ViewGrou protected boolean mIsPageInTransition = false; - protected float mSpringOverScrollX; + protected float mSpringOverScroll; protected boolean mWasInOverscroll = false; - protected int mUnboundedScrollX; + protected int mUnboundedScroll; + + protected int mLayoutRotation = Surface.ROTATION_0; + protected int mDisplayRotation = Surface.ROTATION_0; // Page Indicator @Thunk int mPageIndicatorViewId; @@ -166,11 +183,12 @@ public abstract class PagedView extends ViewGrou * Initializes various states for this workspace. */ protected void init() { - mScroller = new OverScroller(getContext()); + Context context = getContext(); + mScroller = new OverScroller(context); setDefaultInterpolator(Interpolators.SCROLL); mCurrentPage = 0; - final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledPagingTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); @@ -182,6 +200,8 @@ public abstract class PagedView extends ViewGrou if (Utilities.ATLEAST_OREO) { setDefaultFocusHighlightEnabled(false); } + + sFlagForcedRotation = Utilities.isForcedRotation(context); } protected void setDefaultInterpolator(Interpolator interpolator) { @@ -236,12 +256,12 @@ public abstract class PagedView extends ViewGrou */ protected void updateCurrentPageScroll() { // If the current page is invalid, just reset the scroll position to zero - int newX = 0; + int newPosition = 0; if (0 <= mCurrentPage && mCurrentPage < getPageCount()) { - newX = getScrollForPage(mCurrentPage); + newPosition = getScrollForPage(mCurrentPage); } - scrollTo(newX, 0); - mScroller.startScroll(mScroller.getCurrPos(), newX - mScroller.getCurrPos()); + mOrientationHandler.set(this, VIEW_SCROLL_TO, newPosition); + mOrientationHandler.scrollerStartScroll(mScroller, newPosition); forceFinishScroller(true); } @@ -285,7 +305,7 @@ public abstract class PagedView extends ViewGrou int dir = !mIsRtl ? 1 : - 1; int currScroll = getScrollForPage(page); int prevScroll; - while (currScroll < mMinScrollX) { + while (currScroll < mMinScroll) { page += dir; prevScroll = currScroll; currScroll = getScrollForPage(page); @@ -294,7 +314,7 @@ public abstract class PagedView extends ViewGrou break; } } - while (currScroll > mMaxScrollX) { + while (currScroll > mMaxScroll) { page -= dir; prevScroll = currScroll; currScroll = getScrollForPage(page); @@ -378,45 +398,73 @@ public abstract class PagedView extends ViewGrou AccessibilityEvent.TYPE_VIEW_FOCUSED, null); } - protected int getUnboundedScrollX() { - return mUnboundedScrollX; + protected int getUnboundedScroll() { + return mUnboundedScroll; + } + + protected void updateLayoutRotation(int touchRotation) { + setLayoutRotation(touchRotation, mDisplayRotation); + } + + /** @param touchRotation Must be one of {@link android.view.Surface.ROTATION_0/90/180/270} */ + public void setLayoutRotation(int touchRotation, int displayRotation) { + if (mLayoutRotation == touchRotation && mDisplayRotation == displayRotation) { + return; + } + + mOrientationState.update(touchRotation, displayRotation); + mOrientationHandler = mOrientationState.getOrientationHandler(); + mLayoutRotation = touchRotation; + mDisplayRotation = displayRotation; + requestLayout(); + } + + public PagedViewOrientedState getPagedViewOrientedState() { + return mOrientationState; + } + + public PagedOrientationHandler getPagedOrientationHandler() { + return getPagedViewOrientedState().getOrientationHandler(); + } + + public void disableMultipleLayoutRotations(boolean disable) { + mOrientationState.disableMultipleOrientations(disable); + mOrientationHandler = mOrientationState.getOrientationHandler(); + requestLayout(); } @Override public void scrollBy(int x, int y) { - scrollTo(getUnboundedScrollX() + x, getScrollY() + y); + mOrientationHandler.delegateScrollBy(this, getUnboundedScroll(), x, y); } @Override public void scrollTo(int x, int y) { - mUnboundedScrollX = x; + int primaryScroll = mOrientationHandler.getPrimaryValue(x, y); + int secondaryScroll = mOrientationHandler.getSecondaryValue(x, y); + mUnboundedScroll = primaryScroll; - boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < mMinScrollX); - boolean isXAfterLastPage = mIsRtl ? (x < mMinScrollX) : (x > mMaxScrollX); - - if (!isXBeforeFirstPage && !isXAfterLastPage) { - mSpringOverScrollX = 0; + boolean isBeforeFirstPage = mIsRtl ? + (primaryScroll > mMaxScroll) : (primaryScroll < mMinScroll); + boolean isAfterLastPage = mIsRtl ? + (primaryScroll < mMinScroll) : (primaryScroll > mMaxScroll); + if (!isBeforeFirstPage && !isAfterLastPage) { + mSpringOverScroll = 0; } - if (isXBeforeFirstPage) { - super.scrollTo(mIsRtl ? mMaxScrollX : mMinScrollX, y); + if (isBeforeFirstPage) { + mOrientationHandler.delegateScrollTo(this, + secondaryScroll, mIsRtl ? mMaxScroll : mMinScroll); if (mAllowOverScroll) { mWasInOverscroll = true; - if (mIsRtl) { - overScroll(x - mMaxScrollX); - } else { - overScroll(x - mMinScrollX); - } + overScroll(primaryScroll - (mIsRtl ? mMaxScroll : mMinScroll)); } - } else if (isXAfterLastPage) { - super.scrollTo(mIsRtl ? mMinScrollX : mMaxScrollX, y); + } else if (isAfterLastPage) { + mOrientationHandler.delegateScrollTo(this, + secondaryScroll, mIsRtl ? mMinScroll : mMaxScroll); if (mAllowOverScroll) { mWasInOverscroll = true; - if (mIsRtl) { - overScroll(x - mMinScrollX); - } else { - overScroll(x - mMaxScrollX); - } + overScroll(primaryScroll - (mIsRtl ? mMinScroll : mMaxScroll)); } } else { if (mWasInOverscroll) { @@ -425,7 +473,13 @@ public abstract class PagedView extends ViewGrou } super.scrollTo(x, y); } + } + /** + * Helper for {@link PagedOrientationHandler} to be able to call parent's scrollTo method + */ + public void superScrollTo(int x, int y) { + super.scrollTo(x, y); } private void sendScrollAccessibilityEvent() { @@ -436,9 +490,7 @@ public abstract class PagedView extends ViewGrou ev.setScrollable(true); ev.setScrollX(getScrollX()); ev.setScrollY(getScrollY()); - ev.setMaxScrollX(mMaxScrollX); - ev.setMaxScrollY(0); - + mOrientationHandler.setMaxScroll(ev, mMaxScroll); sendAccessibilityEventUnchecked(ev); } } @@ -459,9 +511,10 @@ public abstract class PagedView extends ViewGrou protected boolean computeScrollHelper(boolean shouldInvalidate) { if (mScroller.computeScrollOffset()) { // Don't bother scrolling if the page does not need to be moved - if (getUnboundedScrollX() != mScroller.getCurrPos() - || getScrollX() != mScroller.getCurrPos()) { - scrollTo(mScroller.getCurrPos(), 0); + int currentScroll = mOrientationHandler.getPrimaryScroll(this); + if (mUnboundedScroll != mScroller.getCurrPos() + || currentScroll != mScroller.getCurrPos()) { + mOrientationHandler.set(this, VIEW_SCROLL_TO, mScroller.getCurrPos()); } if (shouldInvalidate) { invalidate(); @@ -580,7 +633,8 @@ public abstract class PagedView extends ViewGrou if (DEBUG) Log.d(TAG, "PagedView.onLayout()"); - if (getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC)) { + boolean isScrollChanged = getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC); + if (isScrollChanged) { pageScrollChanged = true; } @@ -621,7 +675,6 @@ public abstract class PagedView extends ViewGrou /** * Initializes {@code outPageScrolls} with scroll positions for view at that index. The length * of {@code outPageScrolls} should be same as the the childCount - * */ protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren, ComputePageScrollsLogic scrollLogic) { @@ -631,36 +684,30 @@ public abstract class PagedView extends ViewGrou final int endIndex = mIsRtl ? -1 : childCount; final int delta = mIsRtl ? -1 : 1; - final int verticalCenter = (getPaddingTop() + getMeasuredHeight() + mInsets.top - - mInsets.bottom - getPaddingBottom()) / 2; + final int pageCenter = mOrientationHandler.getCenterForPage(this, mInsets); - final int scrollOffsetLeft = mInsets.left + getPaddingLeft(); - final int scrollOffsetRight = getWidth() - getPaddingRight() - mInsets.right; + final int scrollOffsetStart = mOrientationHandler.getScrollOffsetStart(this, mInsets); + final int scrollOffsetEnd = mOrientationHandler.getScrollOffsetEnd(this, mInsets); boolean pageScrollChanged = false; - for (int i = startIndex, childLeft = scrollOffsetLeft; i != endIndex; i += delta) { + for (int i = startIndex, childStart = scrollOffsetStart; i != endIndex; i += delta) { final View child = getPageAt(i); if (scrollLogic.shouldIncludeView(child)) { - final int childWidth = child.getMeasuredWidth(); - final int childRight = childLeft + childWidth; - - if (layoutChildren) { - final int childHeight = child.getMeasuredHeight(); - final int childTop = verticalCenter - childHeight / 2; - child.layout(childLeft, childTop, childRight, childTop + childHeight); - } + ChildBounds bounds = mOrientationHandler.getChildBounds(child, childStart, + pageCenter, layoutChildren); + final int primaryDimension = bounds.primaryDimension; + final int childPrimaryEnd = bounds.childPrimaryEnd; // In case the pages are of different width, align the page to left or right edge // based on the orientation. final int pageScroll = mIsRtl - ? (childLeft - scrollOffsetLeft) - : Math.max(0, childRight - scrollOffsetRight); + ? (childStart - scrollOffsetStart) + : Math.max(0, childPrimaryEnd - scrollOffsetEnd); if (outPageScrolls[i] != pageScroll) { pageScrollChanged = true; outPageScrolls[i] = pageScroll; } - - childLeft += childWidth + mPageSpacing + getChildGap(); + childStart += primaryDimension + mPageSpacing + getChildGap(); } } return pageScrollChanged; @@ -671,15 +718,15 @@ public abstract class PagedView extends ViewGrou } protected void updateMinAndMaxScrollX() { - mMinScrollX = computeMinScrollX(); - mMaxScrollX = computeMaxScrollX(); + mMinScroll = computeMinScroll(); + mMaxScroll = computeMaxScroll(); } - protected int computeMinScrollX() { + protected int computeMinScroll() { return 0; } - protected int computeMaxScrollX() { + protected int computeMaxScroll() { int childCount = getChildCount(); if (childCount > 0) { final int index = mIsRtl ? 0 : childCount - 1; @@ -722,7 +769,8 @@ public abstract class PagedView extends ViewGrou protected int getChildOffset(int index) { if (index < 0 || index > getChildCount() - 1) return 0; - return getPageAt(index).getLeft(); + View pageAtIndex = getPageAt(index); + return mOrientationHandler.getChildStart(pageAtIndex); } @Override @@ -873,13 +921,13 @@ public abstract class PagedView extends ViewGrou case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check - * whether the user has moved far enough from his original down touch. + * whether the user has moved far enough from their original down touch. */ if (mActivePointerId != INVALID_POINTER) { determineScrollingStart(ev); } // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN - // event. in that case, treat the first occurence of a move event as a ACTION_DOWN + // event. in that case, treat the first occurrence of a move event as a ACTION_DOWN // i.e. fall through to the next case (don't break) // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events // while it's small- this was causing a crash before we checked for INVALID_POINTER) @@ -892,9 +940,9 @@ public abstract class PagedView extends ViewGrou // Remember location of down touch mDownMotionX = x; mDownMotionY = y; - mLastMotionX = x; - mLastMotionXRemainder = 0; - mTotalMotionX = 0; + mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0); + mLastMotionRemainder = 0; + mTotalMotion = 0; mActivePointerId = ev.getPointerId(0); updateIsBeingDraggedOnTouchDown(); @@ -956,17 +1004,17 @@ public abstract class PagedView extends ViewGrou final int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) return; - final float x = ev.getX(pointerIndex); - final int xDiff = (int) Math.abs(x - mLastMotionX); + final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev, pointerIndex); + final int diff = (int) Math.abs(primaryDirection - mLastMotion); final int touchSlop = Math.round(touchSlopScale * mTouchSlop); - boolean xMoved = xDiff > touchSlop; + boolean moved = diff > touchSlop; - if (xMoved) { + if (moved) { // Scroll if the user moved far enough along the X axis mIsBeingDragged = true; - mTotalMotionX += Math.abs(mLastMotionX - x); - mLastMotionX = x; - mLastMotionXRemainder = 0; + mTotalMotion += Math.abs(mLastMotion - primaryDirection); + mLastMotion = primaryDirection; + mLastMotionRemainder = 0; onScrollInteractionBegin(); pageBeginTransition(); // Stop listening for things like pinches. @@ -1033,10 +1081,9 @@ public abstract class PagedView extends ViewGrou @Override protected void dispatchDraw(Canvas canvas) { - if (mScroller.isSpringing() && mSpringOverScrollX != 0) { + if (mScroller.isSpringing() && mSpringOverScroll != 0) { int saveCount = canvas.save(); - - canvas.translate(-mSpringOverScrollX, 0); + mOrientationHandler.set(canvas, CANVAS_TRANSLATE, -mSpringOverScroll); super.dispatchDraw(canvas); canvas.restoreToCount(saveCount); @@ -1046,25 +1093,27 @@ public abstract class PagedView extends ViewGrou } protected void dampedOverScroll(int amount) { - mSpringOverScrollX = amount; + mSpringOverScroll = amount; if (amount == 0) { return; } - int overScrollAmount = OverScroll.dampedScroll(amount, getMeasuredWidth()); - mSpringOverScrollX = overScrollAmount; + int size = mOrientationHandler.getMeasuredSize(this); + int overScrollAmount = OverScroll.dampedScroll(amount, size); + mSpringOverScroll = overScrollAmount; if (mScroller.isSpringing()) { invalidate(); return; } - int x = Utilities.boundToRange(getScrollX(), mMinScrollX, mMaxScrollX); - super.scrollTo(x + overScrollAmount, getScrollY()); + int primaryScroll = mOrientationHandler.getPrimaryScroll(this); + int boundedScroll = Utilities.boundToRange(primaryScroll, mMinScroll, mMaxScroll); + mOrientationHandler.delegateScrollTo(this, boundedScroll + overScrollAmount); invalidate(); } protected void overScroll(int amount) { - mSpringOverScrollX = amount; + mSpringOverScroll = amount; if (mScroller.isSpringing()) { invalidate(); return; @@ -1073,11 +1122,8 @@ public abstract class PagedView extends ViewGrou if (amount == 0) return; if (mFreeScroll && !mScroller.isFinished()) { - if (amount < 0) { - super.scrollTo(mMinScrollX + amount, getScrollY()); - } else { - super.scrollTo(mMaxScrollX + amount, getScrollY()); - } + int scrollAmount = amount < 0 ? mMinScroll + amount : mMaxScroll + amount; + mOrientationHandler.delegateScrollTo(this, scrollAmount); } else { dampedOverScroll(amount); } @@ -1127,37 +1173,37 @@ public abstract class PagedView extends ViewGrou } // Remember where the motion event started - mDownMotionX = mLastMotionX = ev.getX(); + mDownMotionX = ev.getX(); mDownMotionY = ev.getY(); - mLastMotionXRemainder = 0; - mTotalMotionX = 0; + mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0); + mLastMotionRemainder = 0; + mTotalMotion = 0; mActivePointerId = ev.getPointerId(0); - if (mIsBeingDragged) { onScrollInteractionBegin(); pageBeginTransition(); } break; - case MotionEvent.ACTION_MOVE: - if (mIsBeingDragged) { + case MotionEvent.ACTION_MOVE: + if (mIsBeingDragged) { // Scroll to follow the motion event final int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) return true; - final float x = ev.getX(pointerIndex); - final float deltaX = mLastMotionX + mLastMotionXRemainder - x; - - mTotalMotionX += Math.abs(deltaX); + float direction = mOrientationHandler.getPrimaryDirection(ev, pointerIndex); + float delta = mLastMotion + mLastMotionRemainder - direction; + mTotalMotion += Math.abs(delta); // Only scroll and update mLastMotionX if we have moved some discrete amount. We // keep the remainder because we are actually testing if we've moved from the last // scrolled position (which is discrete). - if (Math.abs(deltaX) >= 1.0f) { - scrollBy((int) deltaX, 0); - mLastMotionX = x; - mLastMotionXRemainder = deltaX - (int) deltaX; + if (Math.abs(delta) >= 1.0f) { + mLastMotion = direction; + mLastMotionRemainder = delta - (int) delta; + + mOrientationHandler.set(this, VIEW_SCROLL_BY, (int) delta); } else { awakenScrollBars(); } @@ -1170,27 +1216,31 @@ public abstract class PagedView extends ViewGrou if (mIsBeingDragged) { final int activePointerId = mActivePointerId; final int pointerIndex = ev.findPointerIndex(activePointerId); - final float x = ev.getX(pointerIndex); + final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev, + pointerIndex); final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId); - final int deltaX = (int) (x - mDownMotionX); - final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth(); - boolean isSignificantMove = Math.abs(deltaX) > pageWidth * - SIGNIFICANT_MOVE_THRESHOLD; - mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); - boolean isFling = mTotalMotionX > mTouchSlop && shouldFlingForVelocity(velocityX); - boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0; - boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0; + int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker, + mActivePointerId); + int delta = (int) (primaryDirection - mDownMotionPrimary); + int pageOrientedSize = mOrientationHandler.getMeasuredSize(getPageAt(mCurrentPage)); + + boolean isSignificantMove = Math.abs(delta) > pageOrientedSize * + SIGNIFICANT_MOVE_THRESHOLD; + + mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection); + boolean isFling = mTotalMotion > mTouchSlop && shouldFlingForVelocity(velocity); + boolean isDeltaLeft = mIsRtl ? delta > 0 : delta < 0; + boolean isVelocityLeft = mIsRtl ? velocity > 0 : velocity < 0; if (!mFreeScroll) { // In the case that the page is moved far to one direction and then is flung // in the opposite direction, we use a threshold to determine whether we should // just return to the starting page, or if we should skip one further. boolean returnToOriginalPage = false; - if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && - Math.signum(velocityX) != Math.signum(deltaX) && isFling) { + if (Math.abs(delta) > pageOrientedSize * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && + Math.signum(velocity) != Math.signum(delta) && isFling) { returnToOriginalPage = true; } @@ -1199,15 +1249,15 @@ public abstract class PagedView extends ViewGrou // test for a large move if a fling has been registered. That is, a large // move to the left and fling to the right will register as a fling to the right. - if (((isSignificantMove && !isDeltaXLeft && !isFling) || - (isFling && !isVelocityXLeft)) && mCurrentPage > 0) { + if (((isSignificantMove && !isDeltaLeft && !isFling) || + (isFling && !isVelocityLeft)) && mCurrentPage > 0) { finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; - snapToPageWithVelocity(finalPage, velocityX); - } else if (((isSignificantMove && isDeltaXLeft && !isFling) || - (isFling && isVelocityXLeft)) && + snapToPageWithVelocity(finalPage, velocity); + } else if (((isSignificantMove && isDeltaLeft && !isFling) || + (isFling && isVelocityLeft)) && mCurrentPage < getChildCount() - 1) { finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; - snapToPageWithVelocity(finalPage, velocityX); + snapToPageWithVelocity(finalPage, velocity); } else { snapToDestination(); } @@ -1216,38 +1266,40 @@ public abstract class PagedView extends ViewGrou abortScrollerAnimation(true); } - int initialScrollX = getScrollX(); + int initialScroll = mOrientationHandler.getPrimaryScroll(this); + int maxScroll = mMaxScroll; + int minScroll = mMinScroll; - if (((initialScrollX >= mMaxScrollX) && (isVelocityXLeft || !isFling)) || - ((initialScrollX <= mMinScrollX) && (!isVelocityXLeft || !isFling))) { - mScroller.springBack(getScrollX(), mMinScrollX, mMaxScrollX); + if (((initialScroll >= maxScroll) && (isVelocityLeft || !isFling)) || + ((initialScroll <= minScroll) && (!isVelocityLeft || !isFling))) { + mScroller.springBack(initialScroll, minScroll, maxScroll); mNextPage = getPageNearestToCenterOfScreen(); } else { mScroller.setInterpolator(mDefaultInterpolator); - mScroller.fling(initialScrollX, -velocityX, - mMinScrollX, mMaxScrollX, - Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR)); + mScroller.fling(initialScroll, -velocity, + minScroll, maxScroll, + Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR)); - int finalX = mScroller.getFinalPos(); - mNextPage = getPageNearestToCenterOfScreen(finalX); + int finalPos = mScroller.getFinalPos(); + mNextPage = getPageNearestToCenterOfScreen(finalPos); int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1); int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0); - if (finalX > mMinScrollX && finalX < mMaxScrollX) { + if (finalPos > minScroll && finalPos < maxScroll) { // If scrolling ends in the half of the added space that is closer to // the end, settle to the end. Otherwise snap to the nearest page. // If flinging past one of the ends, don't change the velocity as it // will get stopped at the end anyway. - int pageSnappedX = finalX < (firstPageScroll + mMinScrollX) / 2 - ? mMinScrollX - : finalX > (lastPageScroll + mMaxScrollX) / 2 - ? mMaxScrollX - : getScrollForPage(mNextPage); + int pageSnapped = finalPos < (firstPageScroll + minScroll) / 2 + ? minScroll + : finalPos > (lastPageScroll + maxScroll) / 2 + ? maxScroll + : getScrollForPage(mNextPage); - mScroller.setFinalPos(pageSnappedX); + mScroller.setFinalPos(pageSnapped); // Ensure the scroll/snap doesn't happen too fast; int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION - - mScroller.getDuration(); + - mScroller.getDuration(); if (extraScrollDuration > 0) { mScroller.extendDuration(extraScrollDuration); } @@ -1279,8 +1331,8 @@ public abstract class PagedView extends ViewGrou return true; } - protected boolean shouldFlingForVelocity(int velocityX) { - return Math.abs(velocityX) > mFlingThresholdVelocity; + protected boolean shouldFlingForVelocity(int velocity) { + return Math.abs(velocity) > mFlingThresholdVelocity; } private void resetTouchState() { @@ -1364,8 +1416,9 @@ public abstract class PagedView extends ViewGrou // active pointer and adjust accordingly. // TODO: Make this decision more intelligent. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionX = mDownMotionX = ev.getX(newPointerIndex); - mLastMotionXRemainder = 0; + mLastMotion = mDownMotionPrimary = mOrientationHandler.getPrimaryDirection(ev, + newPointerIndex); + mLastMotionRemainder = 0; mActivePointerId = ev.getPointerId(newPointerIndex); if (mVelocityTracker != null) { mVelocityTracker.clear(); @@ -1383,19 +1436,20 @@ public abstract class PagedView extends ViewGrou } public int getPageNearestToCenterOfScreen() { - return getPageNearestToCenterOfScreen(getScrollX()); + return getPageNearestToCenterOfScreen(mOrientationHandler.getPrimaryScroll(this)); } - private int getPageNearestToCenterOfScreen(int scaledScrollX) { - int screenCenter = scaledScrollX + (getMeasuredWidth() / 2); + private int getPageNearestToCenterOfScreen(int scaledScroll) { + int pageOrientationSize = mOrientationHandler.getMeasuredSize(this); + int screenCenter = scaledScroll + (pageOrientationSize / 2); int minDistanceFromScreenCenter = Integer.MAX_VALUE; int minDistanceFromScreenCenterIndex = -1; final int childCount = getChildCount(); for (int i = 0; i < childCount; ++i) { View layout = getPageAt(i); - int childWidth = layout.getMeasuredWidth(); - int halfChildWidth = (childWidth / 2); - int childCenter = getChildOffset(i) + halfChildWidth; + int childSize = mOrientationHandler.getMeasuredSize(layout); + int halfChildSize = (childSize / 2); + int childCenter = getChildOffset(i) + halfChildSize; int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); if (distanceFromScreenCenter < minDistanceFromScreenCenter) { minDistanceFromScreenCenter = distanceFromScreenCenter; @@ -1410,7 +1464,8 @@ public abstract class PagedView extends ViewGrou } protected boolean isInOverScroll() { - return (getScrollX() > mMaxScrollX || getScrollX() < mMinScrollX); + int scroll = mOrientationHandler.getPrimaryScroll(this); + return scroll > mMaxScroll || scroll < mMinScroll; } protected int getPageSnapDuration() { @@ -1432,10 +1487,10 @@ public abstract class PagedView extends ViewGrou protected boolean snapToPageWithVelocity(int whichPage, int velocity) { whichPage = validateNewPage(whichPage); - int halfScreenSize = getMeasuredWidth() / 2; + int halfScreenSize = mOrientationHandler.getMeasuredSize(this) / 2; - final int newX = getScrollForPage(whichPage); - int delta = newX - getUnboundedScrollX(); + final int newLoc = getScrollForPage(whichPage); + int delta = newLoc - getUnboundedScroll(); int duration = 0; if (Math.abs(velocity) < mMinFlingVelocity) { @@ -1462,7 +1517,7 @@ public abstract class PagedView extends ViewGrou if (QUICKSTEP_SPRINGS.get()) { return snapToPage(whichPage, delta, duration, false, null, - velocity * Math.signum(newX - getUnboundedScrollX()), true); + velocity * Math.signum(delta), true); } else { return snapToPage(whichPage, delta, duration); } @@ -1488,8 +1543,8 @@ public abstract class PagedView extends ViewGrou TimeInterpolator interpolator) { whichPage = validateNewPage(whichPage); - int newX = getScrollForPage(whichPage); - final int delta = newX - getUnboundedScrollX(); + int newLoc = getScrollForPage(whichPage); + final int delta = newLoc - getUnboundedScroll(); return snapToPage(whichPage, delta, duration, immediate, interpolator, 0, false); } @@ -1535,9 +1590,9 @@ public abstract class PagedView extends ViewGrou } if (spring && QUICKSTEP_SPRINGS.get()) { - mScroller.startScrollSpring(getUnboundedScrollX(), delta, duration, velocity); + mScroller.startScrollSpring(getUnboundedScroll(), delta, duration, velocity); } else { - mScroller.startScroll(getUnboundedScrollX(), delta, duration); + mScroller.startScroll(getUnboundedScroll(), delta, duration); } updatePageIndicator(); diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java index 7f327a5e5d..403d77913c 100644 --- a/src/com/android/launcher3/ResourceUtils.java +++ b/src/com/android/launcher3/ResourceUtils.java @@ -21,12 +21,12 @@ import android.util.DisplayMetrics; import android.util.TypedValue; public class ResourceUtils { + public static final int DEFAULT_NAVBAR_VALUE = 48; public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width"; public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height"; - public static int getNavbarSize(String resName, Resources res) { - return getDimenByName(resName, res, 48); + return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE); } public static int getDimenByName(String resName, Resources res, int defaultValue) { diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java index 114c49118f..184113413b 100644 --- a/src/com/android/launcher3/SecondaryDropTarget.java +++ b/src/com/android/launcher3/SecondaryDropTarget.java @@ -67,7 +67,6 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList super(context, attrs, defStyle); mCacheExpireAlarm = new Alarm(); - mCacheExpireAlarm.setOnAlarmListener(this); } @Override @@ -75,6 +74,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList super.onAttachedToWindow(); if (mHadPendingAlarm) { mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT); + mCacheExpireAlarm.setOnAlarmListener(this); mHadPendingAlarm = false; } } @@ -84,6 +84,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList super.onDetachedFromWindow(); if (mCacheExpireAlarm.alarmPending()) { mCacheExpireAlarm.cancelAlarm(); + mCacheExpireAlarm.setOnAlarmListener(null); mHadPendingAlarm = true; } } @@ -168,6 +169,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList } // Cancel any pending alarm and set cache expiry after some time mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT); + mCacheExpireAlarm.setOnAlarmListener(this); if (uninstallDisabled) { return false; } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index e0e4cc0ce4..9780630b78 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -17,6 +17,7 @@ package com.android.launcher3; import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED; +import static com.android.launcher3.states.RotationHelper.FIXED_ROTATION_TRANSFORM_SETTING_NAME; import android.animation.ValueAnimator; import android.annotation.TargetApi; @@ -127,6 +128,11 @@ public final class Utilities { Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; } + public static boolean isForcedRotation(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0; + } + // An intent extra to indicate the horizontal scroll of the wallpaper. public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET"; public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR"; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index a8f492f623..590c620020 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -966,8 +966,8 @@ public class Workspace extends PagedView private boolean isScrollingOverlay() { return mLauncherOverlay != null && - ((mIsRtl && getUnboundedScrollX() > mMaxScrollX) - || (!mIsRtl && getUnboundedScrollX() < mMinScrollX)); + ((mIsRtl && getUnboundedScroll() > mMaxScroll) + || (!mIsRtl && getUnboundedScroll() < mMinScroll)); } @Override @@ -1003,7 +1003,7 @@ public class Workspace extends PagedView public void showPageIndicatorAtCurrentScroll() { if (mPageIndicator != null) { - mPageIndicator.setScroll(getScrollX(), computeMaxScrollX()); + mPageIndicator.setScroll(getScrollX(), computeMaxScroll()); } } diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index fcccc598ec..afb72172b8 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -98,8 +98,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo protected boolean mUsingTabs; private boolean mSearchModeWhileUsingTabs = false; - private RecyclerViewFastScroller mTouchHandler; - private final Point mFastScrollerOffset = new Point(); + protected RecyclerViewFastScroller mTouchHandler; + protected final Point mFastScrollerOffset = new Point(); private final MultiValueAlpha mMultiValueAlpha; @@ -238,6 +238,16 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo @Override public boolean onTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + AllAppsRecyclerView rv = getActiveRecyclerView(); + if (rv != null && rv.getScrollbar().isHitInParent(ev.getX(), ev.getY(), + mFastScrollerOffset)) { + mTouchHandler = rv.getScrollbar(); + } else { + mTouchHandler = null; + + } + } if (mTouchHandler != null) { mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset); return true; diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java index 5b73940488..ab4cb6b8db 100644 --- a/src/com/android/launcher3/allapps/AllAppsPagedView.java +++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java @@ -48,7 +48,7 @@ public class AllAppsPagedView extends PagedView { @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); - mPageIndicator.setScroll(l, mMaxScrollX); + mPageIndicator.setScroll(l, mMaxScroll); } @Override diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index e49ff30f20..217916205d 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -32,8 +32,10 @@ import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.anim.SpringObjectAnimator; +import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; +import com.android.systemui.plugins.ResourceProvider; /** * Handles AllApps view transition. @@ -47,9 +49,6 @@ import com.android.launcher3.views.ScrimView; */ public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener { - private static final float SPRING_DAMPING_RATIO = 0.75f; - private static final float SPRING_STIFFNESS = 600f; - public static final FloatProperty ALL_APPS_PROGRESS = new FloatProperty("allAppsProgress") { @@ -187,8 +186,12 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil public Animator createSpringAnimation(float... progressValues) { if (UNSTABLE_SPRINGS.get()) { + ResourceProvider rp = DynamicResource.provider(mLauncher); + float damping = rp.getFloat(R.dimen.all_apps_spring_damping_ratio); + float stiffness = rp.getFloat(R.dimen.all_apps_spring_stiffness); + return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange, - SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues); + damping, stiffness, progressValues); } return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues); } diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java index 25db0e7f1d..f6766c4023 100644 --- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java @@ -51,7 +51,10 @@ public class LauncherAllAppsContainerView extends AllAppsContainerView { public boolean onInterceptTouchEvent(MotionEvent ev) { // The AllAppsContainerView houses the QSB and is hence visible from the Workspace // Overview states. We shouldn't intercept for the scrubber in these cases. - if (!mLauncher.isInState(LauncherState.ALL_APPS)) return false; + if (!mLauncher.isInState(LauncherState.ALL_APPS)) { + mTouchHandler = null; + return false; + } return super.onInterceptTouchEvent(ev); } diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java index eaf3b1cb24..06d0f1c463 100644 --- a/src/com/android/launcher3/anim/FlingSpringAnim.java +++ b/src/com/android/launcher3/anim/FlingSpringAnim.java @@ -15,32 +15,40 @@ */ package com.android.launcher3.anim; +import android.content.Context; + import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; import androidx.dynamicanimation.animation.FlingAnimation; import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; +import com.android.launcher3.R; +import com.android.launcher3.util.DynamicResource; +import com.android.systemui.plugins.ResourceProvider; + /** * Given a property to animate and a target value and starting velocity, first apply friction to * the fling until we pass the target, then apply a spring force to pull towards the target. */ public class FlingSpringAnim { - private static final float FLING_FRICTION = 1.5f; - private static final float SPRING_STIFFNESS = 200; - private static final float SPRING_DAMPING = 0.8f; - private final FlingAnimation mFlingAnim; private SpringAnimation mSpringAnim; private float mTargetPosition; - public FlingSpringAnim(K object, FloatPropertyCompat property, float startPosition, - float targetPosition, float startVelocity, float minVisChange, float minValue, - float maxValue, float springVelocityFactor, OnAnimationEndListener onEndListener) { + public FlingSpringAnim(K object, Context context, FloatPropertyCompat property, + float startPosition, float targetPosition, float startVelocity, float minVisChange, + float minValue, float maxValue, float springVelocityFactor, + OnAnimationEndListener onEndListener) { + ResourceProvider rp = DynamicResource.provider(context); + float damping = rp.getFloat(R.dimen.swipe_up_rect_xy_damping_ratio); + float stiffness = rp.getFloat(R.dimen.swipe_up_rect_xy_stiffness); + float friction = rp.getFloat(R.dimen.swipe_up_rect_xy_fling_friction); + mFlingAnim = new FlingAnimation(object, property) - .setFriction(FLING_FRICTION) + .setFriction(friction) // Have the spring pull towards the target if we've slowed down too much before // reaching it. .setMinimumVisibleChange(minVisChange) @@ -54,8 +62,8 @@ public class FlingSpringAnim { .setStartValue(value) .setStartVelocity(velocity * springVelocityFactor) .setSpring(new SpringForce(mTargetPosition) - .setStiffness(SPRING_STIFFNESS) - .setDampingRatio(SPRING_DAMPING)); + .setStiffness(stiffness) + .setDampingRatio(damping)); mSpringAnim.addEndListener(onEndListener); mSpringAnim.animateToFinalPosition(mTargetPosition); })); diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index d7498a5c11..ed28df0748 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -101,11 +101,15 @@ public final class FeatureFlags { "Adds localized title and keyword search and ranking"); public static final BooleanFlag ENABLE_PREDICTION_DISMISS = new DeviceFlag( - "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list"); + "ENABLE_PREDICTION_DISMISS", true, "Allow option to dimiss apps from predicted list"); public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag( "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture"); + public static final BooleanFlag FORCE_LOCAL_OVERSCROLL_PLUGIN = getDebugFlag( + "FORCE_LOCAL_OVERSCROLL_PLUGIN", false, + "Use a launcher-provided OverscrollPlugin if available"); + public static final BooleanFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = getDebugFlag( "ASSISTANT_GIVES_LAUNCHER_FOCUS", false, "Allow Launcher to handle nav bar gestures while Assistant is running over it"); @@ -113,6 +117,10 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag( "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps"); + public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = new DeviceFlag( + "HOTSEAT_MIGRATE_NEW_PAGE", true, + "Migrates hotseat to a new workspace page instead of same page"); + public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag( "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache"); @@ -120,7 +128,7 @@ public final class FeatureFlags { "MULTI_DB_GRID_MIRATION_ALGO", false, "Use the multi-db grid migration algorithm"); public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag( - "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", false, + "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker"); public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag( @@ -139,6 +147,11 @@ public final class FeatureFlags { "ENABLE_LSQ_VELOCITY_PROVIDER", false, "Use Least Square algorithm for motion pause detection."); + public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS = + getDebugFlag( + "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false, + "Always use hardware optimization for folder animations."); + public static void initialize(Context context) { synchronized (sDebugFlags) { for (DebugFlag flag : sDebugFlags) { diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index e33d89fe4f..69f93de55e 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -26,6 +26,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; +import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS; import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM; import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY; import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED; @@ -546,6 +547,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } private boolean shouldUseHardwareLayerForAnimation(CellLayout currentCellLayout) { + if (ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS.get()) return true; + int folderCount = 0; final ShortcutAndWidgetContainer container = currentCellLayout.getShortcutsAndWidgets(); for (int i = container.getChildCount() - 1; i >= 0; --i) { diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 3b5fd5902c..c6d62f8bc7 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -258,7 +258,7 @@ public class FolderPagedView extends PagedView { @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); - mPageIndicator.setScroll(l, mMaxScrollX); + mPageIndicator.setScroll(l, mMaxScroll); } /** diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 0927b26505..02e98e80df 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -87,9 +87,11 @@ import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.pm.UserCache; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; +import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.ArrayList; import java.util.Arrays; @@ -128,7 +130,8 @@ public class LauncherPreviewRenderer implements Callable { private static final Set WHITELIST = new HashSet<>( Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE, - LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE)); + LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, + CustomWidgetManager.INSTANCE, PluginManagerWrapper.INSTANCE)); private final InvariantDeviceProfile mIdp; private final Map mObjectMap = new HashMap<>(); @@ -145,6 +148,14 @@ public class LauncherPreviewRenderer implements Callable { return this; } + public void onDestroy() { + CustomWidgetManager customWidgetManager = (CustomWidgetManager) mObjectMap.get( + CustomWidgetManager.INSTANCE); + if (customWidgetManager != null) { + customWidgetManager.onDestroy(); + } + } + /** * Find a cached object from mObjectMap if we have already created one. If not, generate * an object using the provider. @@ -349,6 +360,9 @@ public class LauncherPreviewRenderer implements Callable { private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) { WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName( info.providerName); + if (widgetItem == null) { + return; + } AppWidgetHostView view = new AppWidgetHostView(mContext); view.setAppWidget(-1, widgetItem.widgetInfo); view.updateAppWidget(null); @@ -385,9 +399,11 @@ public class LauncherPreviewRenderer implements Callable { } WorkspaceFetcher fetcher; + PreviewContext previewContext = null; if (needsToMigrate && success) { + previewContext = new PreviewContext(mContext, mIdp); LauncherAppState appForPreview = new LauncherAppState( - new PreviewContext(mContext, mIdp), null /* iconCacheFileName */); + previewContext, null /* iconCacheFileName */); fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview); MODEL_EXECUTOR.execute(fetcher); } else { @@ -396,6 +412,9 @@ public class LauncherPreviewRenderer implements Callable { (LauncherModel.ModelUpdateTask) fetcher); } WorkspaceResult workspaceResult = fetcher.get(); + if (previewContext != null) { + previewContext.onDestroy(); + } if (workspaceResult == null) { return; @@ -557,6 +576,7 @@ public class LauncherPreviewRenderer implements Callable { public WorkspaceResult call() throws Exception { List allShortcuts = new ArrayList<>(); loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI); + mBgDataModel.widgetsModel.update(mApp, null); return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets, mBgDataModel.widgetsModel); } diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java index a3fdf8da0b..bfeb1dc3ef 100644 --- a/src/com/android/launcher3/logging/FileLog.java +++ b/src/com/android/launcher3/logging/FileLog.java @@ -38,7 +38,7 @@ public final class FileLog { private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); - private static final long MAX_LOG_FILE_SIZE = 4 << 20; // 4 mb + private static final long MAX_LOG_FILE_SIZE = 8 << 20; // 4 mb private static Handler sHandler = null; private static File sLogsDirectory = null; diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index fbf01fc777..62904ae4a4 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -104,7 +104,7 @@ import java.util.concurrent.CancellationException; public class LoaderTask implements Runnable { private static final String TAG = "LoaderTask"; - private final LauncherAppState mApp; + protected final LauncherAppState mApp; private final AllAppsList mBgAllAppsList; protected final BgDataModel mBgDataModel; diff --git a/src/com/android/launcher3/model/PagedViewOrientedState.java b/src/com/android/launcher3/model/PagedViewOrientedState.java new file mode 100644 index 0000000000..1349effe72 --- /dev/null +++ b/src/com/android/launcher3/model/PagedViewOrientedState.java @@ -0,0 +1,104 @@ +/* + * + * * 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.model; + +import android.view.Surface; + +import com.android.launcher3.states.RotationHelper; +import com.android.launcher3.touch.PortraitPagedViewHandler; +import com.android.launcher3.touch.LandscapePagedViewHandler; +import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.touch.SeascapePagedViewHandler; + +/** + * Container to hold orientation/rotation related information for Launcher. + * This is not meant to be an abstraction layer for applying different functionality between + * the different orientation/rotations. For that see {@link PagedOrientationHandler} + * + * This class has initial default state assuming the device and foreground app have + * no ({@link Surface.ROTATION_0} rotation. + * + * Currently this class resides in {@link com.android.launcher3.PagedView}, but there's a ticket + * to disassociate it from Launcher since it's needed before Launcher is instantiated + * See TODO(b/150300347) + */ +public final class PagedViewOrientedState { + + private PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler(); + + private int mTouchRotation = Surface.ROTATION_0; + private int mDisplayRotation = Surface.ROTATION_0; + /** + * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake + * launcher orientations. + */ + private boolean mDisableMultipleOrientations; + + public void update(int touchRotation, int displayRotation) { + mDisplayRotation = displayRotation; + mTouchRotation = touchRotation; + if (mTouchRotation == Surface.ROTATION_90) { + mOrientationHandler = new LandscapePagedViewHandler(); + } else if (mTouchRotation == Surface.ROTATION_270) { + mOrientationHandler = new SeascapePagedViewHandler(); + } else { + mOrientationHandler = new PortraitPagedViewHandler(); + } + } + + /** + * @return {@code true} if the area where the user touched the nav bar is the expected + * location for the given display rotation. Ex. bottom of phone in portrait, or left side of + * phone in landscape, right side in seascape, etc. + * False otherwise + */ + public boolean isTouchRegionNaturalForDisplay() { + return mTouchRotation == mDisplayRotation; + } + + public boolean areMultipleLayoutOrientationsDisabled() { + return mDisableMultipleOrientations; + } + + public void disableMultipleOrientations(boolean disable) { + mDisableMultipleOrientations = disable; + if (disable) { + mOrientationHandler = new PortraitPagedViewHandler(); + } + } + + public int getDisplayRotation() { + return mDisplayRotation; + } + + /** + * Gets the difference between the rotation of the device/display and which region the + * user is currently interacting with in factors of 90 degree clockwise rotations. + * Ex. Display is in portrait -> 0, user touches landscape region -> 1, this + * method would return 3 because it takes 3 clockwise 90 degree rotations from normal to + * landscape (portrait -> seascape -> reverse portrait -> landscape) + */ + public int getTouchDisplayDelta() { + return RotationHelper.deltaRotation(mTouchRotation, mDisplayRotation); + } + + public PagedOrientationHandler getOrientationHandler() { + return mOrientationHandler; + } +} diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 812268a0f4..b193ffd8cb 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -176,7 +176,7 @@ public class NotificationMainView extends FrameLayout implements SingleAxisSwipe // SingleAxisSwipeDetector.Listener's @Override - public void onDragStart(boolean start) { } + public void onDragStart(boolean start, float startDisplacement) { } @Override diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index 049fda9372..8dc2e611e9 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -96,6 +96,7 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat { initFlags(); loadPluginPrefs(); + maybeAddSandboxCategory(); } @Override @@ -203,6 +204,31 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat { }); } + private void maybeAddSandboxCategory() { + Context context = getContext(); + if (context == null) { + return; + } + Intent launchSandboxIntent = + new Intent("com.android.quickstep.action.GESTURE_SANDBOX") + .setPackage(context.getPackageName()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (launchSandboxIntent.resolveActivity(context.getPackageManager()) == null) { + return; + } + PreferenceCategory sandboxCategory = newCategory("Sandbox"); + Preference launchSandboxPreference = new Preference(context); + launchSandboxPreference.setKey("launchSandbox"); + launchSandboxPreference.setTitle("Launch Gesture Navigation Sandbox"); + launchSandboxPreference.setSummary( + "This provides tutorials and a place to practice navigation gestures."); + launchSandboxPreference.setOnPreferenceClickListener(preference -> { + startActivity(launchSandboxIntent); + return true; + }); + sandboxCategory.addPreference(launchSandboxPreference); + } + private String toName(String action) { String str = action.replace("com.android.systemui.action.PLUGIN_", "") .replace("com.android.launcher3.action.PLUGIN_", ""); diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java index 852928b416..43d54eb28b 100644 --- a/src/com/android/launcher3/states/RotationHelper.java +++ b/src/com/android/launcher3/states/RotationHelper.java @@ -17,20 +17,35 @@ package com.android.launcher3.states; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import android.content.ContentResolver; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.provider.Settings; +import android.view.MotionEvent; +import android.view.Surface; import android.view.WindowManager; import com.android.launcher3.Launcher; +import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.UiThreadHelper; +import java.util.ArrayList; +import java.util.List; + /** * Utility class to manage launcher rotation */ @@ -38,15 +53,39 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation"; + public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform"; + private final ContentResolver mContentResolver; + + /** + * Listener to receive changes when {@link #FIXED_ROTATION_TRANSFORM_SETTING_NAME} flag changes. + */ + public interface ForcedRotationChangedListener { + void onForcedRotationChanged(boolean isForcedRotation); + } + public static boolean getAllowRotationDefaultValue() { - // If the device was scaled, used the original dimensions to determine if rotation - // is allowed of not. + // If the device's pixel density was scaled (usually via settings for A11y), use the + // original dimensions to determine if rotation is allowed of not. Resources res = Resources.getSystem(); int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp * res.getDisplayMetrics().densityDpi / DENSITY_DEVICE_STABLE; return originalSmallestWidth >= 600; } + + private final ContentObserver mContentObserver = + new ContentObserver(MAIN_EXECUTOR.getHandler()) { + @Override + public void onChange(boolean selfChange) { + boolean forcedRotation = Utilities.isForcedRotation(mLauncher); + PagedView.sFlagForcedRotation = forcedRotation; + updateForcedRotation(); + for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) { + listener.onForcedRotationChanged(forcedRotation); + } + } + }; + public static final int REQUEST_NONE = 0; public static final int REQUEST_ROTATE = 1; public static final int REQUEST_LOCK = 2; @@ -56,6 +95,8 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { private boolean mIgnoreAutoRotateSettings; private boolean mAutoRotateEnabled; + private boolean mForcedRotation; + private List mForcedRotationChangedListeners = new ArrayList<>(); /** * Rotation request made by @@ -84,6 +125,7 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { // On large devices we do not handle auto-rotate differently. mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation); + updateForcedRotation(); if (!mIgnoreAutoRotateSettings) { mPrefs = Utilities.getPrefs(mLauncher); mPrefs.registerOnSharedPreferenceChangeListener(this); @@ -92,6 +134,26 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { } else { mPrefs = null; } + + // TODO(b/150260456) Add this in home settings as well + mContentResolver = launcher.getContentResolver(); + mContentResolver.registerContentObserver(Settings.Global.getUriFor( + FIXED_ROTATION_TRANSFORM_SETTING_NAME), false, mContentObserver); + } + + private void updateForcedRotation() { + mForcedRotation = !getAllowRotationDefaultValue() && Utilities.isForcedRotation(mLauncher); + } + + /** + * will not be called when first registering the listener. + */ + public void addForcedRotationCallback(ForcedRotationChangedListener listener) { + mForcedRotationChangedListeners.add(listener); + } + + public void removeForcedRotationCallback(ForcedRotationChangedListener listener) { + mForcedRotationChangedListeners.remove(listener); } public void setRotationHadDifferentUI(boolean rotationHasDifferentUI) { @@ -173,6 +235,10 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { if (mPrefs != null) { mPrefs.unregisterOnSharedPreferenceChangeListener(this); } + if (mContentResolver != null) { + mContentResolver.unregisterContentObserver(mContentObserver); + } + mForcedRotationChangedListeners.clear(); } } @@ -182,7 +248,10 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { } final int activityFlags; - if (mStateHandlerRequest != REQUEST_NONE) { + if (mForcedRotation) { + // TODO(b/150214193) Properly address this + activityFlags = SCREEN_ORIENTATION_PORTRAIT; + } else if (mStateHandlerRequest != REQUEST_NONE) { activityFlags = mStateHandlerRequest == REQUEST_LOCK ? SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED; } else if (mCurrentTransitionRequest != REQUEST_NONE) { @@ -204,6 +273,118 @@ public class RotationHelper implements OnSharedPreferenceChangeListener { } } + public static int getDegreesFromRotation(int rotation) { + int degrees; + switch (rotation) { + case Surface.ROTATION_90: + degrees = 90; + break; + case Surface.ROTATION_180: + degrees = 180; + break; + case Surface.ROTATION_270: + degrees = 270; + break; + case Surface.ROTATION_0: + default: + degrees = 0; + break; + } + return degrees; + } + + public static int getRotationFromDegrees(int degrees) { + int threshold = 70; + if (degrees >= (360 - threshold) || degrees < (threshold)) { + return Surface.ROTATION_0; + } else if (degrees < (90 + threshold)) { + return Surface.ROTATION_270; + } else if (degrees < 180 + threshold) { + return Surface.ROTATION_180; + } else { + return Surface.ROTATION_90; + } + } + + /** + * @return how many factors {@param newRotation} is rotated 90 degrees clockwise. + * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise... + * A value of 0 means no rotation has been applied + */ + public static int deltaRotation(int oldRotation, int newRotation) { + int delta = newRotation - oldRotation; + if (delta < 0) delta += 4; + return delta; + } + + /** + * Creates a matrix to transform the given motion event specified by degrees. + * If {@param inverse} is {@code true}, the inverse of that matrix will be applied + */ + public static void transformEvent(float degrees, MotionEvent ev, boolean inverse) { + Matrix transform = new Matrix(); + transform.setRotate(degrees); + if (inverse) { + Matrix inv = new Matrix(); + transform.invert(inv); + ev.transform(inv); + } else { + ev.transform(transform); + } + // TODO: Add scaling back in based on degrees +// if (getWidth() > 0 && getHeight() > 0) { +// float scale = ((float) getWidth()) / getHeight(); +// transform.postScale(scale, 1 / scale); +// } + } + + /** + * TODO(b/149658423): Have {@link com.android.quickstep.OrientationTouchTransformer + * also use this} + */ + public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) { + Matrix m = new Matrix(); + switch (displayRotation) { + case Surface.ROTATION_0: + return m; + case Surface.ROTATION_90: + m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation)); + m.postTranslate(0, screenWidth); + break; + case Surface.ROTATION_270: + m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation)); + m.postTranslate(screenHeight, 0); + break; + } + return m; + } + + public static void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight, + int displayRotation) { + Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation); + m.mapRect(src); + } + + public static void mapInverseRectFromNormalOrientation(RectF src, int screenWidth, + int screenHeight, int displayRotation) { + Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation); + Matrix inverse = new Matrix(); + m.invert(inverse); + inverse.mapRect(src); + } + + public static void getTargetRectForRotation(Rect srcOut, int screenWidth, int screenHeight, + int displayRotation) { + RectF wrapped = new RectF(srcOut); + Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation); + m.mapRect(wrapped); + wrapped.round(srcOut); + } + + public static boolean isRotationLandscape(int rotation) { + return rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90; + } + @Override public String toString() { return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d," diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 9df6241a5b..34d69e9eec 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -230,7 +230,7 @@ public abstract class AbstractStateChangeTouchController } @Override - public void onDragStart(boolean start) { + public void onDragStart(boolean start, float startDisplacement) { mStartState = mLauncher.getStateManager().getState(); mIsLogContainerSet = false; if (mCurrentAnimation == null) { diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java index 30283dab8d..1276ece7e2 100644 --- a/src/com/android/launcher3/touch/BaseSwipeDetector.java +++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java @@ -236,7 +236,7 @@ public abstract class BaseSwipeDetector { } else { mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop; mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop; - } + } } protected abstract boolean shouldScrollStart(PointF displacement); diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java new file mode 100644 index 0000000000..1db65b9705 --- /dev/null +++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.touch; + +import android.content.res.Resources; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.FloatProperty; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.VelocityTracker; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState.ScaleAndTranslation; +import com.android.launcher3.PagedView; +import com.android.launcher3.Utilities; +import com.android.launcher3.states.RotationHelper; +import com.android.launcher3.util.OverScroller; + +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; + +public class LandscapePagedViewHandler implements PagedOrientationHandler { + + @Override + public int getPrimaryValue(int x, int y) { + return y; + } + + @Override + public int getSecondaryValue(int x, int y) { + return x; + } + + @Override + public void delegateScrollTo(PagedView pagedView, int secondaryScroll, int minMaxScroll) { + pagedView.superScrollTo(secondaryScroll, minMaxScroll); + } + + @Override + public void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y) { + pagedView.scrollTo(pagedView.getScrollX() + x, unboundedScroll + y); + } + + @Override + public void scrollerStartScroll(OverScroller scroller, int newPosition) { + scroller.startScroll(scroller.getCurrPos(), newPosition - scroller.getCurrPos()); + } + + @Override + public CurveProperties getCurveProperties(PagedView pagedView, Rect mInsets) { + int scroll = pagedView.getScrollY(); + final int halfPageSize = pagedView.getNormalChildHeight() / 2; + final int screenCenter = mInsets.top + pagedView.getPaddingTop() + scroll + halfPageSize; + final int halfScreenSize = pagedView.getMeasuredHeight() / 2; + return new CurveProperties(scroll, halfPageSize, screenCenter, halfScreenSize); + } + + @Override + public boolean isGoingUp(float displacement) { + return displacement > 0; + } + + @Override + public void adjustFloatingIconStartVelocity(PointF velocity) { + float oldX = velocity.x; + float oldY = velocity.y; + velocity.set(-oldY, oldX); + } + + @Override + public void delegateScrollTo(PagedView pagedView, int primaryScroll) { + pagedView.superScrollTo(pagedView.getScrollX(), primaryScroll); + } + + @Override + public void set(T target, Int2DAction action, int param) { + action.call(target, 0, param); + } + + @Override + public void set(T target, Float2DAction action, float param) { + action.call(target, 0, param); + } + + @Override + public float getPrimaryDirection(MotionEvent event, int pointerIndex) { + return event.getY(pointerIndex); + } + + @Override + public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) { + return velocityTracker.getYVelocity(pointerId); + } + + @Override + public int getMeasuredSize(View view) { + return view.getMeasuredHeight(); + } + + @Override + public int getPrimarySize(Rect rect) { + return rect.height(); + } + + @Override + public float getPrimarySize(RectF rect) { + return rect.height(); + } + + @Override + public int getSecondaryDimension(View view) { + return view.getWidth(); + } + + @Override + public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) { + float offscreenTranslationY = dp.heightPx - view.getPaddingTop(); + return new ScaleAndTranslation(1f, 0f, offscreenTranslationY); + } + + @Override + public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) { + return scaleAndTranslation.translationY; + } + + @Override + public FloatProperty getPrimaryViewTranslate() { + return VIEW_TRANSLATE_Y; + } + + @Override + public FloatProperty getSecondaryViewTranslate() { + return VIEW_TRANSLATE_X; + } + + @Override + public void setPrimaryAndResetSecondaryTranslate(View view, float translation) { + view.setTranslationX(0); + view.setTranslationY(translation); + } + + @Override + public float getViewCenterPosition(View view) { + return view.getTop() + view.getTranslationY(); + } + + @Override + public int getPrimaryScroll(View view) { + return view.getScrollY(); + } + + @Override + public float getPrimaryScale(View view) { + return view.getScaleY(); + } + + @Override + public void setMaxScroll(AccessibilityEvent event, int maxScroll) { + event.setMaxScrollY(maxScroll); + } + + @Override + public boolean getRecentsRtlSetting(Resources resources) { + return !Utilities.isRtl(resources); + } + + @Override + public float getDegreesRotated() { + return 90; + } + + @Override + public void offsetTaskRect(RectF rect, float value, int displayRotation) { + if (displayRotation == Surface.ROTATION_0) { + rect.offset(0, value); + } else if (displayRotation == Surface.ROTATION_90) { + rect.offset(value, 0); + } else if (displayRotation == Surface.ROTATION_180) { + rect.offset(0, -value); + } else { + rect.offset(-value, 0); + } + } + + @Override + public int getChildStart(View view) { + return view.getTop(); + } + + @Override + public int getCenterForPage(View view, Rect insets) { + return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left + - insets.right - view.getPaddingRight()) / 2; + } + + @Override + public int getScrollOffsetStart(View view, Rect insets) { + return insets.top + view.getPaddingTop(); + } + + @Override + public int getScrollOffsetEnd(View view, Rect insets) { + return view.getHeight() - view.getPaddingBottom() - insets.bottom; + } + + @Override + public SingleAxisSwipeDetector.Direction getOppositeSwipeDirection() { + return HORIZONTAL; + } + + @Override + public int getShortEdgeLength(DeviceProfile dp) { + return dp.heightPx; + } + + @Override + public int getTaskDismissDirectionFactor() { + return 1; + } + + @Override + public ChildBounds getChildBounds(View child, int childStart, int pageCenter, + boolean layoutChild) { + final int childHeight = child.getMeasuredHeight(); + final int childBottom = childStart + childHeight; + final int childWidth = child.getMeasuredWidth(); + final int childLeft = pageCenter - childWidth/ 2; + if (layoutChild) { + child.layout(childLeft, childStart, childLeft + childWidth, childBottom); + } + return new ChildBounds(childHeight, childWidth, childBottom, childLeft); + } +} diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java new file mode 100644 index 0000000000..b4802cda90 --- /dev/null +++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java @@ -0,0 +1,124 @@ +/* + * 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.touch; + +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.FloatProperty; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState; +import com.android.launcher3.PagedView; +import com.android.launcher3.util.OverScroller; + +/** + * Abstraction layer to separate horizontal and vertical specific implementations + * for {@link com.android.launcher3.PagedView}. Majority of these implementations are (should be) as + * simple as choosing the correct X and Y analogous methods. + */ +public interface PagedOrientationHandler { + + interface Int2DAction { + void call(T target, int x, int y); + } + interface Float2DAction { + void call(T target, float x, float y); + } + Int2DAction VIEW_SCROLL_BY = View::scrollBy; + Int2DAction VIEW_SCROLL_TO = View::scrollTo; + Float2DAction CANVAS_TRANSLATE = Canvas::translate; + void set(T target, Int2DAction action, int param); + void set(T target, Float2DAction action, float param); + float getPrimaryDirection(MotionEvent event, int pointerIndex); + float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId); + int getMeasuredSize(View view); + int getPrimarySize(Rect rect); + float getPrimarySize(RectF rect); + int getSecondaryDimension(View view); + LauncherState.ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view); + float getTranslationValue(LauncherState.ScaleAndTranslation scaleAndTranslation); + FloatProperty getPrimaryViewTranslate(); + FloatProperty getSecondaryViewTranslate(); + void setPrimaryAndResetSecondaryTranslate(View view, float translation); + float getViewCenterPosition(View view); + int getPrimaryScroll(View view); + float getPrimaryScale(View view); + int getChildStart(View view); + int getCenterForPage(View view, Rect insets); + int getScrollOffsetStart(View view, Rect insets); + int getScrollOffsetEnd(View view, Rect insets); + SingleAxisSwipeDetector.Direction getOppositeSwipeDirection(); + int getShortEdgeLength(DeviceProfile dp); + int getTaskDismissDirectionFactor(); + ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild); + void setMaxScroll(AccessibilityEvent event, int maxScroll); + boolean getRecentsRtlSetting(Resources resources); + float getDegreesRotated(); + void offsetTaskRect(RectF rect, float value, int delta); + int getPrimaryValue(int x, int y); + int getSecondaryValue(int x, int y); + void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll); + /** Uses {@params pagedView}.getScroll[X|Y]() method for the secondary amount*/ + void delegateScrollTo(PagedView pagedView, int primaryScroll); + void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y); + void scrollerStartScroll(OverScroller scroller, int newPosition); + CurveProperties getCurveProperties(PagedView pagedView, Rect insets); + boolean isGoingUp(float displacement); + + /** + * Maps the velocity from the coordinate plane of the foreground app to that + * of Launcher's (which now will always be portrait) + */ + void adjustFloatingIconStartVelocity(PointF velocity); + + class CurveProperties { + public final int scroll; + public final int halfPageSize; + public final int screenCenter; + public final int halfScreenSize; + + public CurveProperties(int scroll, int halfPageSize, int screenCenter, int halfScreenSize) { + this.scroll = scroll; + this.halfPageSize = halfPageSize; + this.screenCenter = screenCenter; + this.halfScreenSize = halfScreenSize; + } + } + + class ChildBounds { + + public final int primaryDimension; + public final int secondaryDimension; + public final int childPrimaryEnd; + public final int childSecondaryEnd; + + ChildBounds(int primaryDimension, int secondaryDimension, int childPrimaryEnd, + int childSecondaryEnd) { + this.primaryDimension = primaryDimension; + this.secondaryDimension = secondaryDimension; + this.childPrimaryEnd = childPrimaryEnd; + this.childSecondaryEnd = childSecondaryEnd; + } + } +} diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java new file mode 100644 index 0000000000..22eee49df6 --- /dev/null +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.touch; + +import android.content.res.Resources; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.FloatProperty; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.VelocityTracker; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState.ScaleAndTranslation; +import com.android.launcher3.PagedView; +import com.android.launcher3.Utilities; +import com.android.launcher3.states.RotationHelper; +import com.android.launcher3.util.OverScroller; + +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL; + +public class PortraitPagedViewHandler implements PagedOrientationHandler { + + @Override + public int getPrimaryValue(int x, int y) { + return x; + } + + @Override + public int getSecondaryValue(int x, int y) { + return y; + } + + @Override + public void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll) { + pagedView.superScrollTo(primaryScroll, secondaryScroll); + } + + @Override + public void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y) { + pagedView.scrollTo(unboundedScroll + x, pagedView.getScrollY() + y); + } + + @Override + public void scrollerStartScroll(OverScroller scroller, int newPosition) { + scroller.startScroll(newPosition - scroller.getCurrPos(), scroller.getCurrPos()); + } + + @Override + public CurveProperties getCurveProperties(PagedView pagedView, Rect mInsets) { + int scroll = pagedView.getScrollX(); + final int halfPageSize = pagedView.getNormalChildWidth() / 2; + final int screenCenter = mInsets.left + pagedView.getPaddingLeft() + scroll + halfPageSize; + final int halfScreenSize = pagedView.getMeasuredWidth() / 2; + return new CurveProperties(scroll, halfPageSize, screenCenter, halfScreenSize); + } + + @Override + public boolean isGoingUp(float displacement) { + return displacement < 0; + } + + @Override + public void adjustFloatingIconStartVelocity(PointF velocity) { + //no-op + } + + @Override + public void delegateScrollTo(PagedView pagedView, int primaryScroll) { + pagedView.superScrollTo(primaryScroll, pagedView.getScrollY()); + } + + @Override + public void set(T target, Int2DAction action, int param) { + action.call(target, param, 0); + } + + @Override + public void set(T target, Float2DAction action, float param) { + action.call(target, param, 0); + } + + @Override + public float getPrimaryDirection(MotionEvent event, int pointerIndex) { + return event.getX(pointerIndex); + } + + @Override + public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) { + return velocityTracker.getXVelocity(pointerId); + } + + @Override + public int getMeasuredSize(View view) { + return view.getMeasuredWidth(); + } + + @Override + public int getPrimarySize(Rect rect) { + return rect.width(); + } + + @Override + public float getPrimarySize(RectF rect) { + return rect.width(); + } + + @Override + public int getSecondaryDimension(View view) { + return view.getHeight(); + } + + @Override + public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) { + float offscreenTranslationX = dp.widthPx - view.getPaddingStart(); + return new ScaleAndTranslation(1f, offscreenTranslationX, 0f); + } + + @Override + public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) { + return scaleAndTranslation.translationX; + } + + @Override + public FloatProperty getPrimaryViewTranslate() { + return VIEW_TRANSLATE_X; + } + + @Override + public FloatProperty getSecondaryViewTranslate() { + return VIEW_TRANSLATE_Y; + } + + @Override + public void setPrimaryAndResetSecondaryTranslate(View view, float translation) { + view.setTranslationX(translation); + view.setTranslationY(0); + } + + @Override + public float getViewCenterPosition(View view) { + return view.getLeft() + view.getTranslationX(); + } + + @Override + public int getPrimaryScroll(View view) { + return view.getScrollX(); + } + + @Override + public float getPrimaryScale(View view) { + return view.getScaleX(); + } + + @Override + public void setMaxScroll(AccessibilityEvent event, int maxScroll) { + event.setMaxScrollX(maxScroll); + } + + @Override + public boolean getRecentsRtlSetting(Resources resources) { + return !Utilities.isRtl(resources); + } + + @Override + public float getDegreesRotated() { + return 0; + } + + @Override + public void offsetTaskRect(RectF rect, float value, int displayRotation) { + if (displayRotation == Surface.ROTATION_0) { + rect.offset(value, 0); + } else if (displayRotation == Surface.ROTATION_90) { + rect.offset(0, -value); + } else if (displayRotation == Surface.ROTATION_180) { + rect.offset(-value, 0); + } else { + rect.offset(0, value); + } + } + + @Override + public int getChildStart(View view) { + return view.getLeft(); + } + + @Override + public int getCenterForPage(View view, Rect insets) { + return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top + - insets.bottom - view.getPaddingBottom()) / 2; + } + + @Override + public int getScrollOffsetStart(View view, Rect insets) { + return insets.left + view.getPaddingLeft(); + } + + @Override + public int getScrollOffsetEnd(View view, Rect insets) { + return view.getWidth() - view.getPaddingRight() - insets.right; + } + + @Override + public SingleAxisSwipeDetector.Direction getOppositeSwipeDirection() { + return VERTICAL; + } + + @Override + public int getShortEdgeLength(DeviceProfile dp) { + return dp.widthPx; + } + + @Override + public int getTaskDismissDirectionFactor() { + return -1; + } + + @Override + public ChildBounds getChildBounds(View child, int childStart, int pageCenter, + boolean layoutChild) { + final int childWidth = child.getMeasuredWidth(); + final int childRight = childStart + childWidth; + final int childHeight = child.getMeasuredHeight(); + final int childTop = pageCenter - childHeight / 2; + if (layoutChild) { + child.layout(childStart, childTop, childRight, childTop + childHeight); + } + return new ChildBounds(childWidth, childHeight, childRight, childTop); + } +} diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java new file mode 100644 index 0000000000..eebd87fc86 --- /dev/null +++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.touch; + +import android.content.res.Resources; +import android.graphics.PointF; +import android.graphics.RectF; +import android.view.Surface; + +import com.android.launcher3.Utilities; + +public class SeascapePagedViewHandler extends LandscapePagedViewHandler { + + @Override + public int getTaskDismissDirectionFactor() { + return -1; + } + + @Override + public boolean getRecentsRtlSetting(Resources resources) { + return Utilities.isRtl(resources); + } + + @Override + public void offsetTaskRect(RectF rect, float value, int displayRotation) { + if (displayRotation == Surface.ROTATION_0) { + rect.offset(0, value); + } else if (displayRotation == Surface.ROTATION_90) { + rect.offset(value, 0); + } else if (displayRotation == Surface.ROTATION_180) { + rect.offset(0, -value); + } else { + rect.offset(-value, 0); + } + } + + @Override + public float getDegreesRotated() { + return 270; + } + + @Override + public boolean isGoingUp(float displacement) { + return displacement < 0; + } + + @Override + public void adjustFloatingIconStartVelocity(PointF velocity) { + float oldX = velocity.x; + float oldY = velocity.y; + velocity.set(oldY, -oldX); + } +} diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java index 9d406f3bef..d72548619f 100644 --- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java +++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java @@ -148,7 +148,8 @@ public class SingleAxisSwipeDetector extends BaseSwipeDetector { @Override protected void reportDragStartInternal(boolean recatch) { - mListener.onDragStart(!recatch); + float startDisplacement = mDir.extractDirection(mSubtractDisplacement); + mListener.onDragStart(!recatch, startDisplacement); } @Override @@ -165,8 +166,13 @@ public class SingleAxisSwipeDetector extends BaseSwipeDetector { /** Listener to receive updates on the swipe. */ public interface Listener { - /** @param start whether this was the original drag start, as opposed to a recatch. */ - void onDragStart(boolean start); + /** + * TODO(b/150256055) consolidate all the different onDrag() methods into one + * @param start whether this was the original drag start, as opposed to a recatch. + * @param startDisplacement the initial touch displacement for the primary direction as + * given by by {@link Direction#extractDirection(PointF)} + */ + void onDragStart(boolean start, float startDisplacement); boolean onDrag(float displacement); diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java index d3dac04f98..f18e411d36 100644 --- a/src/com/android/launcher3/util/DefaultDisplay.java +++ b/src/com/android/launcher3/util/DefaultDisplay.java @@ -28,6 +28,8 @@ import android.util.Log; import android.view.Display; import android.view.WindowManager; +import androidx.annotation.VisibleForTesting; + import java.util.ArrayList; /** @@ -127,6 +129,18 @@ public class DefaultDisplay implements DisplayListener { public final DisplayMetrics metrics; + @VisibleForTesting + public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize, + Point largestSize, DisplayMetrics metrics) { + this.id = id; + this.rotation = rotation; + this.singleFrameMs = singleFrameMs; + this.realSize = realSize; + this.smallestSize = smallestSize; + this.largestSize = largestSize; + this.metrics = metrics; + } + private Info(Context context) { this(context.getSystemService(WindowManager.class).getDefaultDisplay()); } diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java index 8a75767f42..e6ee1864dc 100644 --- a/src/com/android/launcher3/util/DynamicResource.java +++ b/src/com/android/launcher3/util/DynamicResource.java @@ -68,6 +68,11 @@ public class DynamicResource implements ResourceProvider, PluginListener { diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index bdba39c371..11c10293e4 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -149,8 +149,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView /* SingleAxisSwipeDetector.Listener */ @Override - public void onDragStart(boolean start) { - } + public void onDragStart(boolean start, float startDisplacement) { } @Override public boolean onDrag(float displacement) { diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 6038873c42..895f8de5a9 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -33,6 +33,7 @@ import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.View; import android.view.View.OnClickListener; +import android.widget.RemoteViews; import com.android.launcher3.DeviceProfile; import com.android.launcher3.FastBitmapDrawable; @@ -93,6 +94,15 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView } } + @Override + public void updateAppWidget(RemoteViews remoteViews) { + super.updateAppWidget(remoteViews); + WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(getContext()); + if (widgetManagerHelper.isAppWidgetRestored(mInfo.appWidgetId)) { + reInflate(); + } + } + @Override public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight) { diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java index 8b08d7757c..f3c7822d0b 100644 --- a/src/com/android/launcher3/widget/WidgetManagerHelper.java +++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java @@ -49,6 +49,9 @@ import java.util.stream.Stream; */ public class WidgetManagerHelper { + //TODO: replace this with OPTION_APPWIDGET_RESTORE_COMPLETED b/63667276 + public static final String WIDGET_OPTION_RESTORE_COMPLETED = "appWidgetRestoreCompleted"; + final AppWidgetManager mAppWidgetManager; final Context mContext; @@ -127,6 +130,14 @@ public class WidgetManagerHelper { return null; } + /** + * Returns if a AppWidgetProvider has marked a widget restored + */ + public boolean isAppWidgetRestored(int appWidgetId) { + return !WidgetsModel.GO_DISABLE_WIDGETS && mAppWidgetManager.getAppWidgetOptions( + appWidgetId).getBoolean(WIDGET_OPTION_RESTORE_COMPLETED); + } + public static Map getAllProvidersMap(Context context) { if (WidgetsModel.GO_DISABLE_WIDGETS) { return Collections.emptyMap(); diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java index 2aed9369e3..0ea7d854ee 100644 --- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java +++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java @@ -51,6 +51,7 @@ public class CustomWidgetManager implements PluginListener { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(CustomWidgetManager::new); + private final Context mContext; /** * auto provider Id is an ever-increasing number that serves as the providerId whenever a new * custom widget has been connected. @@ -62,6 +63,7 @@ public class CustomWidgetManager implements PluginListener { private Consumer mWidgetRefreshCallback; private CustomWidgetManager(Context context) { + mContext = context; mPlugins = new SparseArray<>(); mCustomWidgets = new ArrayList<>(); mWidgetsIdMap = new SparseArray<>(); @@ -69,6 +71,10 @@ public class CustomWidgetManager implements PluginListener { .addPluginListener(this, CustomWidgetPlugin.class, true); } + public void onDestroy() { + PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this); + } + @Override public void onPluginConnected(CustomWidgetPlugin plugin, Context context) { mPlugins.put(mAutoProviderId, plugin); diff --git a/src_plugins/com/android/systemui/plugins/ResourceProvider.java b/src_plugins/com/android/systemui/plugins/ResourceProvider.java index eaed9e7f1c..d1767a0931 100644 --- a/src_plugins/com/android/systemui/plugins/ResourceProvider.java +++ b/src_plugins/com/android/systemui/plugins/ResourceProvider.java @@ -44,4 +44,9 @@ public interface ResourceProvider extends Plugin { * @see android.content.res.Resources#getColor(int) */ int getColor(int resId); + + /** + * @see android.content.res.Resources#getFloat(int) + */ + float getFloat(int resId); } diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java index ae32692809..0b99e7a898 100644 --- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java +++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java @@ -249,6 +249,10 @@ public class WidgetsModel { ComponentName providerName) { ArrayList widgetsList = mWidgetsList.get( new PackageItemInfo(providerName.getPackageName())); + if (widgetsList == null) { + return null; + } + for (WidgetItem item : widgetsList) { if (item.componentName.equals(providerName)) { return item; diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java index b0ece77c9b..472e1a1c91 100644 --- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java +++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java @@ -89,7 +89,7 @@ public class SingleAxisSwipeDetectorTest { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 - mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDragStart(anyBoolean()); + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); } @Test @@ -99,7 +99,7 @@ public class SingleAxisSwipeDetectorTest { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDragStart(anyBoolean()); + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); } @Test @@ -107,7 +107,7 @@ public class SingleAxisSwipeDetectorTest { mGenerator.put(0, 100, 100); mGenerator.move(0, 100 + mTouchSlop, 100); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener, never()).onDragStart(anyBoolean()); + verify(mMockListener, never()).onDragStart(anyBoolean(), anyFloat()); } @Test @@ -118,7 +118,7 @@ public class SingleAxisSwipeDetectorTest { mGenerator.put(0, 100, 100); mGenerator.move(0, 100 + mTouchSlop, 100); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDragStart(anyBoolean()); + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); } @Test @@ -129,7 +129,7 @@ public class SingleAxisSwipeDetectorTest { mGenerator.put(0, 100, 100); mGenerator.move(0, 100 - mTouchSlop, 100); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDragStart(anyBoolean()); + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); } @Test @@ -140,7 +140,7 @@ public class SingleAxisSwipeDetectorTest { mGenerator.put(0, 100, 100); mGenerator.move(0, 100 - mTouchSlop, 100); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDragStart(anyBoolean()); + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); } @Test @@ -151,7 +151,7 @@ public class SingleAxisSwipeDetectorTest { mGenerator.put(0, 100, 100); mGenerator.move(0, 100 + mTouchSlop, 100); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDragStart(anyBoolean()); + verify(mMockListener).onDragStart(anyBoolean(), anyFloat()); } @Test diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index e93df96521..6a4b81f171 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -34,10 +34,13 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.os.Debug; import android.os.Process; import android.os.RemoteException; import android.os.StrictMode; +import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.By; @@ -68,6 +71,7 @@ import com.android.launcher3.util.rule.ShellCommandRule; import com.android.launcher3.util.rule.TestStabilityRule; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.rules.RuleChain; @@ -117,6 +121,14 @@ public abstract class AbstractLauncherUiTest { // so let's just mark the fact that the leak has happened. if (sDetectedActivityLeak == null) { sDetectedActivityLeak = violation.toString(); + try { + Debug.dumpHprofData( + getInstrumentation().getTargetContext() + .getFilesDir().getPath() + + "/ActivityLeakHeapDump.hprof"); + } catch (IOException e) { + Log.e(TAG, "dumpHprofData failed", e); + } } }); StrictMode.setVmPolicy(builder.build()); @@ -126,18 +138,6 @@ public abstract class AbstractLauncherUiTest { public static void checkDetectedLeaks() { if (sDetectedActivityLeak != null && !sActivityLeakReported) { sActivityLeakReported = true; - - final UiDevice device = UiDevice.getInstance(getInstrumentation()); - try { - device.executeShellCommand( - "am dumpheap " - + device.getLauncherPackageName() - + " " - + getInstrumentation().getTargetContext().getFilesDir().getPath() - + "/ActivityLeakHeapDump.hprof"); - } catch (IOException e) { - throw new RuntimeException(e); - } } } @@ -215,6 +215,21 @@ public abstract class AbstractLauncherUiTest { @Before public void setUp() throws Exception { + final String launcherPackageName = mDevice.getLauncherPackageName(); + try { + final Context context = InstrumentationRegistry.getContext(); + final PackageManager pm = context.getPackageManager(); + final PackageInfo launcherPackage = pm.getPackageInfo(launcherPackageName, 0); + + if (!launcherPackage.versionName.equals("BuildFromAndroidStudio")) { + Assert.assertEquals("Launcher version doesn't match tests version", + pm.getPackageInfo(context.getPackageName(), 0).getLongVersionCode(), + launcherPackage.getLongVersionCode()); + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + mLauncherPid = 0; // Disable app tracker AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker()); diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index 21a654f43b..793af48e29 100644 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -21,6 +21,7 @@ import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOption import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.appwidget.AppWidgetHost; @@ -33,6 +34,7 @@ import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Bundle; +import android.widget.RemoteViews; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -41,6 +43,7 @@ import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.LauncherSettings; +import com.android.launcher3.R; import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.AbstractLauncherUiTest; @@ -86,7 +89,8 @@ public class BindWidgetTest extends AbstractLauncherUiTest { // Clear all existing data LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); - LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); + LauncherSettings.Settings.call(mResolver, + LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); } @After @@ -172,6 +176,26 @@ public class BindWidgetTest extends AbstractLauncherUiTest { assertNotNull(AppWidgetManager.getInstance(mTargetContext) .getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex( LauncherSettings.Favorites.APPWIDGET_ID)))); + + // send OPTION_APPWIDGET_RESTORE_COMPLETED + int appWidgetId = mCursor.getInt( + mCursor.getColumnIndex(LauncherSettings.Favorites.APPWIDGET_ID)); + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mTargetContext); + + Bundle b = new Bundle(); + b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true); + RemoteViews remoteViews = new RemoteViews(mTargetPackage, R.layout.appwidget_not_ready); + appWidgetManager.updateAppWidgetOptions(appWidgetId, b); + appWidgetManager.updateAppWidget(appWidgetId, remoteViews); + + + // verify changes are reflected + waitForLauncherCondition("App widget options did not update", + l -> appWidgetManager.getAppWidgetOptions(appWidgetId).getBoolean( + WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED)); + executeOnLauncher(l -> l.getAppWidgetHost().startListening()); + verifyWidgetPresent(info); + assertNull(mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT)); } @Test @@ -254,6 +278,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { /** * Creates a LauncherAppWidgetInfo corresponding to {@param info} + * * @param bindWidget if true the info is bound and a valid widgetId is assigned to * the LauncherAppWidgetInfo */ @@ -306,7 +331,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName)); return packages; }); - while(true) { + while (true) { try { mTargetContext.getPackageManager().getPackageInfo( pkg, PackageManager.GET_UNINSTALLED_PACKAGES); @@ -316,7 +341,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { } } pkg = invalidPackage + count; - count ++; + count++; } LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10, new ComponentName(pkg, "com.test.widgetprovider")); diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java index 33a6cf9766..1dbba6a1f6 100644 --- a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java +++ b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java @@ -64,6 +64,7 @@ public class SimpleActivityRule implements TestRule { mBase.evaluate(); } finally { app.unregisterActivityLifecycleCallbacks(this); + mActivity = null; } } diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 6b2fb49d51..d77d392fb8 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -145,7 +145,7 @@ public final class LauncherInstrumentation { private static final String WORKSPACE_RES_ID = "workspace"; private static final String APPS_RES_ID = "apps_view"; - private static final String OVERVIEW_RES_ID = "overview_panel_recents"; + private static final String OVERVIEW_RES_ID = "overview_panel"; private static final String WIDGETS_RES_ID = "widgets_list_view"; private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container"; public static final int WAIT_TIME_MS = 10000; @@ -331,21 +331,25 @@ public final class LauncherInstrumentation { } private String getSystemAnomalyMessage() { - UiObject2 object = mDevice.findObject(By.res("android", "alertTitle")); - if (object != null) { - return "System alert popup is visible: " + object.getText(); + try { + UiObject2 object = mDevice.findObject(By.res("android", "alertTitle")); + if (object != null) { + return "System alert popup is visible: " + object.getText(); + } + + object = mDevice.findObject(By.res("android", "message")); + if (object != null) { + return "Message popup by " + object.getApplicationPackage() + " is visible: " + + object.getText(); + } + + if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked"; + + if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty"; + } catch (Throwable e) { + Log.w(TAG, "getSystemAnomalyMessage failed", e); } - object = mDevice.findObject(By.res("android", "message")); - if (object != null) { - return "Message popup by " + object.getApplicationPackage() + " is visible: " - + object.getText(); - } - - if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked"; - - if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty"; - return null; }