Merge "Simplifying scrim drawable initialization." into ub-launcher3-master
diff --git a/.gitignore b/.gitignore
index 7240e48..694b40c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,5 @@
local.properties
gradle/
build/
-gradlew*
\ No newline at end of file
+gradlew*
+.DS_Store
diff --git a/Android.mk b/Android.mk
index fbe19b0..5614e25 100644
--- a/Android.mk
+++ b/Android.mk
@@ -101,6 +101,7 @@
LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
+ $(call all-java-files-under, src_shortcuts_overrides) \
$(call all-java-files-under, src_ui_overrides) \
$(call all-java-files-under, src_flags)
@@ -131,7 +132,7 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, src_ui_overrides) \
- $(call all-java-files-under, go/src_flags)
+ $(call all-java-files-under, go/src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/go/res
@@ -174,7 +175,8 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, quickstep/src) \
- $(call all-java-files-under, src_flags)
+ $(call all-java-files-under, src_flags) \
+ $(call all-java-files-under, src_shortcuts_overrides)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
LOCAL_PROGUARD_ENABLED := disabled
@@ -235,7 +237,7 @@
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, quickstep/src) \
- $(call all-java-files-under, go/src_flags)
+ $(call all-java-files-under, go/src)
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/quickstep/res \
diff --git a/build.gradle b/build.gradle
index 1b9df53..476e92b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -72,7 +72,7 @@
sourceSets {
main {
res.srcDirs = ['res']
- java.srcDirs = ['src']
+ java.srcDirs = ['src', 'src_shortcuts_overrides']
manifest.srcFile 'AndroidManifest-common.xml'
proto {
srcDir 'protos/'
@@ -100,7 +100,7 @@
l3go {
res.srcDirs = ['go/res']
- java.srcDirs = ['go/src_flags', "src_ui_overrides"]
+ java.srcDirs = ['go/src', "src_ui_overrides"]
manifest.srcFile "go/AndroidManifest.xml"
}
diff --git a/go/src_flags/com/android/launcher3/config/FeatureFlags.java b/go/src/com/android/launcher3/config/FeatureFlags.java
similarity index 100%
rename from go/src_flags/com/android/launcher3/config/FeatureFlags.java
rename to go/src/com/android/launcher3/config/FeatureFlags.java
diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
new file mode 100644
index 0000000..ff0c907
--- /dev/null
+++ b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shortcuts;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.launcher3.ItemInfo;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
+ */
+public class DeepShortcutManager {
+ private static DeepShortcutManager sInstance;
+ private static final Object sInstanceLock = new Object();
+
+ public static DeepShortcutManager getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new DeepShortcutManager(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+ }
+
+ private DeepShortcutManager(Context context) {
+ }
+
+ public static boolean supportsShortcuts(ItemInfo info) {
+ return false;
+ }
+
+ public boolean wasLastCallSuccess() {
+ return false;
+ }
+
+ public void onShortcutsChanged(List<ShortcutInfoCompat> shortcuts) {
+ }
+
+ /**
+ * Queries for the shortcuts with the package name and provided ids.
+ *
+ * This method is intended to get the full details for shortcuts when they are added or updated,
+ * because we only get "key" fields in onShortcutsChanged().
+ */
+ public List<ShortcutInfoCompat> queryForFullDetails(String packageName,
+ List<String> shortcutIds, UserHandle user) {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Gets all the manifest and dynamic shortcuts associated with the given package and user,
+ * to be displayed in the shortcuts container on long press.
+ */
+ public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
+ UserHandle user) {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Removes the given shortcut from the current list of pinned shortcuts.
+ * (Runs on background thread)
+ */
+ public void unpinShortcut(final ShortcutKey key) {
+ }
+
+ /**
+ * Adds the given shortcut to the current list of pinned shortcuts.
+ * (Runs on background thread)
+ */
+ public void pinShortcut(final ShortcutKey key) {
+ }
+
+ public void startShortcut(String packageName, String id, Rect sourceBounds,
+ Bundle startActivityOptions, UserHandle user) {
+ }
+
+ public Drawable getShortcutIconDrawable(ShortcutInfoCompat shortcutInfo, int density) {
+ return null;
+ }
+
+ /**
+ * Returns the id's of pinned shortcuts associated with the given package and user.
+ *
+ * If packageName is null, returns all pinned shortcuts regardless of package.
+ */
+ public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName, UserHandle user) {
+ return Collections.emptyList();
+ }
+
+ public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
+ List<String> shortcutIds, UserHandle user) {
+ return Collections.emptyList();
+ }
+
+ public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandle user) {
+ return Collections.emptyList();
+ }
+
+ public boolean hasHostPermission() {
+ return false;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 5680a67..d7bbfe0 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -178,6 +178,14 @@
@Override
public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
if (hasControlRemoteAppTransitionPermission()) {
+ boolean fromRecents = mLauncher.getStateManager().getState().overviewUi
+ && findTaskViewToLaunch(launcher, v, null) != null;
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ if (fromRecents && recentsView.getQuickScrubController().isQuickSwitch()) {
+ return ActivityOptions.makeCustomAnimation(mLauncher, R.anim.no_anim,
+ R.anim.no_anim);
+ }
+
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
true /* startAtFrontOfQueue */) {
@@ -218,8 +226,6 @@
}
};
- boolean fromRecents = mLauncher.getStateManager().getState().overviewUi
- && findTaskViewToLaunch(launcher, v, null) != null;
int duration = fromRecents
? RECENTS_LAUNCH_DURATION
: APP_LAUNCH_DURATION;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index 2645302..1d65a54 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -35,6 +35,7 @@
* Vertical transition of the task previews relative to the full container.
*/
public static final float OVERVIEW_TRANSLATION_FACTOR = 0.4f;
+ public static final float OVERVIEW_CENTERED_TRANSLATION_FACTOR = 0.5f;
private static final int STATE_FLAGS = FLAG_DISABLE_RESTORE | FLAG_DISABLE_INTERACTION
| FLAG_OVERVIEW_UI | FLAG_HIDE_BACK_BUTTON | FLAG_DISABLE_ACCESSIBILITY;
@@ -60,12 +61,17 @@
RecentsView recentsView = launcher.getOverviewPanel();
recentsView.getTaskSize(sTempRect);
- return new float[] {getOverviewScale(launcher.getDeviceProfile(), sTempRect, launcher),
- OVERVIEW_TRANSLATION_FACTOR};
+ boolean isQuickSwitch = recentsView.getQuickScrubController().isQuickSwitch();
+ float translationYFactor = isQuickSwitch
+ ? OVERVIEW_CENTERED_TRANSLATION_FACTOR
+ : OVERVIEW_TRANSLATION_FACTOR;
+ return new float[] {getOverviewScale(launcher.getDeviceProfile(), sTempRect, launcher,
+ isQuickSwitch), translationYFactor};
}
- public static float getOverviewScale(DeviceProfile dp, Rect taskRect, Context context) {
- if (dp.isVerticalBarLayout()) {
+ public static float getOverviewScale(DeviceProfile dp, Rect taskRect, Context context,
+ boolean isQuickSwitch) {
+ if (dp.isVerticalBarLayout() && !isQuickSwitch) {
return 1f;
}
@@ -73,6 +79,10 @@
float usedHeight = taskRect.height() + res.getDimension(R.dimen.task_thumbnail_top_margin);
float usedWidth = taskRect.width() + 2 * (res.getDimension(R.dimen.recents_page_spacing)
+ res.getDimension(R.dimen.quickscrub_adjacent_visible_width));
+ if (isQuickSwitch) {
+ usedWidth = taskRect.width();
+ return Math.max(dp.availableHeightPx / usedHeight, dp.availableWidthPx / usedWidth);
+ }
return Math.min(Math.min(dp.availableHeightPx / usedHeight,
dp.availableWidthPx / usedWidth), MAX_PREVIEW_SCALE_UP);
}
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index c809e28..85eed1f 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -16,7 +16,6 @@
package com.android.quickstep;
import static android.view.View.TRANSLATION_Y;
-
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
@@ -58,6 +57,7 @@
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.uioverrides.FastOverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -192,7 +192,8 @@
@InteractionType int interactionType, TransformedRect outRect) {
LayoutUtils.calculateLauncherTaskSize(context, dp, outRect.rect);
if (interactionType == INTERACTION_QUICK_SCRUB) {
- outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context);
+ outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context,
+ FeatureFlags.QUICK_SWITCH.get());
}
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index 3420767..c44ccd3 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -16,8 +16,18 @@
package com.android.quickstep;
+import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.FloatProperty;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.animation.Interpolator;
@@ -37,8 +47,10 @@
* The behavior is to evenly divide the progress into sections, each of which scrolls one page.
* The first and last section set an alarm to auto-advance backwards or forwards, respectively.
*/
+@TargetApi(Build.VERSION_CODES.P)
public class QuickScrubController implements OnAlarmListener {
+ public static final int QUICK_SWITCH_FROM_APP_START_DURATION = 0;
public static final int QUICK_SCRUB_FROM_APP_START_DURATION = 240;
public static final int QUICK_SCRUB_FROM_HOME_START_DURATION = 200;
// We want the translation y to finish faster than the rest of the animation.
@@ -52,6 +64,19 @@
0.05f, 0.20f, 0.35f, 0.50f, 0.65f, 0.80f, 0.95f
};
+ private static final FloatProperty<QuickScrubController> PROGRESS
+ = new FloatProperty<QuickScrubController>("progress") {
+ @Override
+ public void setValue(QuickScrubController quickScrubController, float progress) {
+ quickScrubController.onQuickScrubProgress(progress);
+ }
+
+ @Override
+ public Float get(QuickScrubController quickScrubController) {
+ return quickScrubController.mEndProgress;
+ }
+ };
+
private static final String TAG = "QuickScrubController";
private static final boolean ENABLE_AUTO_ADVANCE = true;
private static final long AUTO_ADVANCE_DELAY = 500;
@@ -72,6 +97,13 @@
private ActivityControlHelper mActivityControlHelper;
private TouchInteractionLog mTouchInteractionLog;
+ private boolean mIsQuickSwitch;
+ private float mStartProgress;
+ private float mEndProgress;
+ private float mPrevProgressDelta;
+ private float mPrevPrevProgressDelta;
+ private boolean mShouldSwitchToNext;
+
public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
mActivity = activity;
mRecentsView = recentsView;
@@ -91,17 +123,26 @@
mActivityControlHelper = controlHelper;
mTouchInteractionLog = touchInteractionLog;
+ if (mIsQuickSwitch) {
+ mShouldSwitchToNext = true;
+ mPrevProgressDelta = 0;
+ if (mRecentsView.getTaskViewCount() > 0) {
+ mRecentsView.getTaskViewAt(0).setFullscreen(true);
+ }
+ if (mRecentsView.getTaskViewCount() > 1) {
+ mRecentsView.getTaskViewAt(1).setFullscreen(true);
+ }
+ }
+
snapToNextTaskIfAvailable();
mActivity.getUserEventDispatcher().resetActionDurationMillis();
}
public void onQuickScrubEnd() {
mInQuickScrub = false;
- if (ENABLE_AUTO_ADVANCE) {
- mAutoAdvanceAlarm.cancelAlarm();
- }
- int page = mRecentsView.getNextPage();
+
Runnable launchTaskRunnable = () -> {
+ int page = mRecentsView.getPageNearestToCenterOfScreen();
TaskView taskView = mRecentsView.getTaskViewAt(page);
if (taskView != null) {
mWaitingForTaskLaunch = true;
@@ -118,12 +159,49 @@
TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key));
}
mWaitingForTaskLaunch = false;
+ if (mIsQuickSwitch) {
+ mIsQuickSwitch = false;
+ if (mRecentsView.getTaskViewCount() > 0) {
+ mRecentsView.getTaskViewAt(0).setFullscreen(false);
+ }
+ if (mRecentsView.getTaskViewCount() > 1) {
+ mRecentsView.getTaskViewAt(1).setFullscreen(false);
+ }
+ }
+
}, taskView.getHandler());
} else {
breakOutOfQuickScrub();
}
mActivityControlHelper = null;
};
+
+ if (mIsQuickSwitch) {
+ float progressVelocity = mPrevPrevProgressDelta / SINGLE_FRAME_MS;
+ // Move to the next frame immediately, then start the animation from the
+ // following frame since it starts a frame later.
+ float singleFrameProgress = progressVelocity * SINGLE_FRAME_MS;
+ float fromProgress = mEndProgress + singleFrameProgress;
+ onQuickScrubProgress(fromProgress);
+ fromProgress += singleFrameProgress;
+ float toProgress = mShouldSwitchToNext ? 1 : 0;
+ int duration = (int) Math.abs((toProgress - fromProgress) / progressVelocity);
+ duration = Utilities.boundToRange(duration, 80, 300);
+ Animator anim = ObjectAnimator.ofFloat(this, PROGRESS, fromProgress, toProgress);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ launchTaskRunnable.run();
+ }
+ });
+ anim.setDuration(duration).start();
+ return;
+ }
+
+ if (ENABLE_AUTO_ADVANCE) {
+ mAutoAdvanceAlarm.cancelAlarm();
+ }
+ int page = mRecentsView.getNextPage();
int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
* QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
if (mRecentsView.getChildCount() > 0 && mRecentsView.snapToPage(page, snapDuration)) {
@@ -151,19 +229,28 @@
mLaunchingTaskId = 0;
}
+ public boolean prepareQuickScrub(String tag) {
+ return prepareQuickScrub(tag, mIsQuickSwitch);
+ }
+
/**
* Initializes the UI for quick scrub, returns true if success.
*/
- public boolean prepareQuickScrub(String tag) {
+ public boolean prepareQuickScrub(String tag, boolean isQuickSwitch) {
if (mWaitingForTaskLaunch || mInQuickScrub) {
Log.d(tag, "Waiting for last scrub to finish, will skip this interaction");
return false;
}
mOnFinishedTransitionToQuickScrubRunnable = null;
mRecentsView.setNextPageSwitchRunnable(null);
+ mIsQuickSwitch = isQuickSwitch;
return true;
}
+ public boolean isQuickSwitch() {
+ return mIsQuickSwitch;
+ }
+
public boolean isWaitingForTaskLaunch() {
return mWaitingForTaskLaunch;
}
@@ -179,6 +266,40 @@
}
public void onQuickScrubProgress(float progress) {
+ if (mIsQuickSwitch) {
+ TaskView currentPage = mRecentsView.getTaskViewAt(0);
+ TaskView nextPage = mRecentsView.getTaskViewAt(1);
+ if (currentPage == null || nextPage == null) {
+ return;
+ }
+ if (!mFinishedTransitionToQuickScrub) {
+ mStartProgress = mEndProgress = progress;
+ } else {
+ float progressDelta = progress - mEndProgress;
+ mEndProgress = progress;
+ progress = Utilities.boundToRange(progress, mStartProgress, 1);
+ progress = Utilities.mapToRange(progress, mStartProgress, 1, 0, 1, LINEAR);
+ if (mInQuickScrub) {
+ mShouldSwitchToNext = mPrevProgressDelta > 0.007f || progressDelta > 0.007f
+ || progress >= 0.5f;
+ }
+ mPrevPrevProgressDelta = mPrevProgressDelta;
+ mPrevProgressDelta = progressDelta;
+ float scrollDiff = nextPage.getWidth() + mRecentsView.getPageSpacing();
+ int scrollDir = mRecentsView.isRtl() ? -1 : 1;
+ int linearScrollDiff = (int) (progress * scrollDiff * scrollDir);
+ float accelScrollDiff = ACCEL.getInterpolation(progress) * scrollDiff * scrollDir;
+ currentPage.setZoomScale(1 - DEACCEL_3.getInterpolation(progress)
+ * TaskView.EDGE_SCALE_DOWN_FACTOR);
+ currentPage.setTranslationX(linearScrollDiff + accelScrollDiff);
+ nextPage.setTranslationZ(1);
+ nextPage.setTranslationY(currentPage.getTranslationY());
+ int startScroll = mRecentsView.isRtl() ? mRecentsView.getMaxScrollX() : 0;
+ mRecentsView.setScrollX(startScroll + linearScrollDiff);
+ }
+ return;
+ }
+
int quickScrubSection = 0;
for (float threshold : QUICK_SCRUB_THRESHOLDS) {
if (progress < threshold) {
@@ -228,9 +349,14 @@
public void snapToNextTaskIfAvailable() {
if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
- int duration = mStartedFromHome ? QUICK_SCRUB_FROM_HOME_START_DURATION
- : QUICK_SCRUB_FROM_APP_START_DURATION;
- int pageToGoTo = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
+ int duration = mIsQuickSwitch
+ ? QUICK_SWITCH_FROM_APP_START_DURATION
+ : mStartedFromHome
+ ? QUICK_SCRUB_FROM_HOME_START_DURATION
+ : QUICK_SCRUB_FROM_APP_START_DURATION;
+ int pageToGoTo = mStartedFromHome || mIsQuickSwitch
+ ? 0
+ : mRecentsView.getNextPage() + 1;
goToPageWithHaptic(pageToGoTo, duration, true /* forceHaptic */,
QUICK_SCRUB_START_INTERPOLATOR);
}
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 9ea8884..a604da0 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
+import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -59,6 +60,7 @@
import com.android.launcher3.anim.AnimationSuccessListener;
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.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -979,12 +981,14 @@
setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
// Start the window animation without waiting for launcher.
- animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR,
- true /* goingToHome */);
+ long duration = FeatureFlags.QUICK_SWITCH.get()
+ ? QUICK_SWITCH_FROM_APP_START_DURATION
+ : QUICK_SCRUB_FROM_APP_START_DURATION;
+ animateToProgress(mCurrentShift.value, 1f, duration, LINEAR, true /* goingToHome */);
}
private void onQuickScrubStartUi() {
- if (!mQuickScrubController.prepareQuickScrub(TAG)) {
+ if (!mQuickScrubController.prepareQuickScrub(TAG, FeatureFlags.QUICK_SWITCH.get())) {
mQuickScrubBlocked = true;
setStateOnUiThread(STATE_RESUME_LAST_TASK | STATE_HANDLER_INVALIDATED);
return;
@@ -993,6 +997,7 @@
mLauncherTransitionController.getAnimationPlayer().end();
mLauncherTransitionController = null;
}
+ mLayoutListener.finish();
mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false,
mTouchInteractionLog);
@@ -1008,6 +1013,13 @@
mQuickScrubController.onFinishedTransitionToQuickScrub();
mRecentsView.animateUpRunningTaskIconScale();
+ if (mQuickScrubController.isQuickSwitch()) {
+ TaskView runningTask = mRecentsView.getRunningTaskView();
+ if (runningTask != null) {
+ runningTask.setTranslationY(-mActivity.getResources().getDimension(
+ R.dimen.task_thumbnail_half_top_margin) * 1f / mRecentsView.getScaleX());
+ }
+ }
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index ce65de1..c92c8d6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -17,7 +17,6 @@
package com.android.quickstep.views;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
-
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -33,7 +32,7 @@
import android.util.FloatProperty;
import android.util.Property;
import android.view.View;
-
+import android.view.ViewGroup;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -78,6 +77,7 @@
private final Matrix mMatrix = new Matrix();
private float mClipBottom = -1;
+ private Rect mScaledInsets = new Rect();
private Task mTask;
private ThumbnailData mThumbnailData;
@@ -179,7 +179,17 @@
@Override
protected void onDraw(Canvas canvas) {
- drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
+ if (((TaskView) getParent()).isFullscreen()) {
+ // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
+ drawOnCanvas(canvas,
+ -mScaledInsets.left,
+ -mScaledInsets.top,
+ getMeasuredWidth() + mScaledInsets.right,
+ getMeasuredHeight() + mScaledInsets.bottom,
+ mCornerRadius);
+ } else {
+ drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
+ }
}
public float getCornerRadius() {
@@ -253,6 +263,9 @@
: getMeasuredWidth() / thumbnailWidth;
}
+ mScaledInsets.set(thumbnailInsets);
+ Utilities.scaleRect(mScaledInsets, thumbnailScale);
+
if (rotate) {
int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1;
mMatrix.setRotate(90 * rotationDir);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2a4226f..07d8b89 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -87,7 +87,7 @@
/**
* How much to scale down pages near the edge of the screen.
*/
- private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
+ public static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
public static final long SCALE_ICON_DURATION = 120;
private static final long DIM_ANIM_DURATION = 700;
@@ -142,6 +142,7 @@
private IconView mIconView;
private float mCurveScale;
private float mZoomScale;
+ private boolean mIsFullscreen;
private Animator mIconAndDimAnimator;
private float mFocusTransitionProgress = 1;
@@ -509,4 +510,18 @@
Log.w(tag, msg);
Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show();
}
+
+ /**
+ * Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
+ */
+ public void setFullscreen(boolean isFullscreen) {
+ mIsFullscreen = isFullscreen;
+ mIconView.setVisibility(mIsFullscreen ? INVISIBLE : VISIBLE);
+ setClipChildren(!mIsFullscreen);
+ setClipToPadding(!mIsFullscreen);
+ }
+
+ public boolean isFullscreen() {
+ return mIsFullscreen;
+ }
}
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
new file mode 100644
index 0000000..5011764
--- /dev/null
+++ b/robolectric_tests/Android.mk
@@ -0,0 +1,53 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#############################################
+# Launcher Robolectric test target. #
+#############################################
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := LauncherRoboTests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ androidx.test.runner \
+ androidx.test.rules \
+ mockito-robolectric-prebuilt \
+ truth-prebuilt
+LOCAL_JAVA_LIBRARIES := \
+ platform-robolectric-3.6.1-prebuilt
+
+LOCAL_INSTRUMENTATION_FOR := Launcher3
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+############################################
+# Target to run the previous target. #
+############################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := RunLauncherRoboTests
+LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES := \
+ LauncherRoboTests
+
+LOCAL_TEST_PACKAGE := Launcher3
+
+LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \
+
+LOCAL_ROBOTEST_TIMEOUT := 36000
+
+include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
new file mode 100644
index 0000000..faec380
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Robolectric unit tests for {@link IntSet}
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = 26)
+public class IntSetTest {
+
+ @Test
+ public void shouldBeEmptyInitially() {
+ IntSet set = new IntSet();
+ assertThat(set.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void oneElementSet() {
+ IntSet set = new IntSet();
+ set.add(2);
+ assertThat(set.size()).isEqualTo(1);
+ assertTrue(set.contains(2));
+ assertFalse(set.contains(1));
+ }
+
+
+ @Test
+ public void twoElementSet() {
+ IntSet set = new IntSet();
+ set.add(2);
+ set.add(1);
+ assertThat(set.size()).isEqualTo(2);
+ assertTrue(set.contains(2));
+ assertTrue(set.contains(1));
+ }
+
+ @Test
+ public void threeElementSet() {
+ IntSet set = new IntSet();
+ set.add(2);
+ set.add(1);
+ set.add(10);
+ assertThat(set.size()).isEqualTo(3);
+ assertEquals("1, 2, 10", set.mArray.toConcatString());
+ }
+
+
+ @Test
+ public void duplicateEntries() {
+ IntSet set = new IntSet();
+ set.add(2);
+ set.add(2);
+ assertEquals(1, set.size());
+ }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 36b9e97..9470635 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -625,6 +625,10 @@
mMaxScrollX = computeMaxScrollX();
}
+ public int getMaxScrollX() {
+ return mMaxScrollX;
+ }
+
protected int computeMaxScrollX() {
int childCount = getChildCount();
if (childCount > 0) {
@@ -640,6 +644,10 @@
requestLayout();
}
+ public int getPageSpacing() {
+ return mPageSpacing;
+ }
+
private void dispatchPageCountChanged() {
if (mPageIndicator != null) {
mPageIndicator.setMarkersCount(getChildCount());
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 64b5652..e5a8a01 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -22,9 +22,6 @@
import android.content.SharedPreferences;
import android.provider.Settings;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.Keep;
-
import com.android.launcher3.Utilities;
import java.util.ArrayList;
@@ -32,6 +29,9 @@
import java.util.SortedMap;
import java.util.TreeMap;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.Keep;
+
/**
* Defines a set of flags used to control various launcher behaviors.
*
@@ -87,6 +87,9 @@
// trying to make them fit the orientation the device is in.
public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
+ public static final TogglableFlag QUICK_SWITCH = new TogglableFlag("QUICK_SWITCH", false,
+ "Swiping right on the nav bar while in an app switches to the previous app");
+
/**
* Feature flag to handle define config changes dynamically instead of killing the process.
*/
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
similarity index 100%
rename from src/com/android/launcher3/shortcuts/DeepShortcutManager.java
rename to src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 532d3e8..bc5aaee 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -296,11 +296,6 @@
Process.myUserHandle()).get(0);
}
- protected LauncherActivityInfo getChromeApp() {
- return LauncherAppsCompat.getInstance(mTargetContext)
- .getActivityList("com.android.chrome", Process.myUserHandle()).get(0);
- }
-
/**
* Broadcast receiver which blocks until the result is received.
*/
diff --git a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
index d7a7f6b..e95801a 100644
--- a/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
+++ b/tests/src/com/android/launcher3/ui/ShortcutsLaunchTest.java
@@ -1,22 +1,20 @@
package com.android.launcher3.ui;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.content.pm.LauncherActivityInfo;
-import android.graphics.Point;
+
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
-import android.view.MotionEvent;
-import com.android.launcher3.R;
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.popup.ArrowPopup;
+import com.android.launcher3.tapl.AppIconMenu;
+import com.android.launcher3.tapl.AppIconMenuItem;
+import com.android.launcher3.views.OptionsPopupView;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -27,47 +25,33 @@
@RunWith(AndroidJUnit4.class)
public class ShortcutsLaunchTest extends AbstractLauncherUiTest {
- @Test
- @Ignore
- public void testAppLauncher_portrait() throws Exception {
- lockRotation(true);
- performTest();
+ private boolean isOptionsPopupVisible(Launcher launcher) {
+ final ArrowPopup popup = OptionsPopupView.getOptionsPopup(launcher);
+ return popup != null && popup.isShown();
}
@Test
- @Ignore
- public void testAppLauncher_landscape() throws Exception {
- lockRotation(false);
- performTest();
- }
-
- private void performTest() throws Exception {
+ @PortraitLandscape
+ public void testAppLauncher() throws Exception {
mActivityMonitor.startLauncher();
- LauncherActivityInfo testApp = getSettingsApp();
+ final LauncherActivityInfo testApp = getSettingsApp();
- // Open all apps and wait for load complete
- final UiObject2 appsContainer = TestViewHelpers.openAllApps();
- Wait.atMost(null, Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT);
+ final AppIconMenu menu = mLauncher.
+ pressHome().
+ switchToAllApps().
+ getAppIcon(testApp.getLabel().toString()).
+ openMenu();
- // Find settings app and verify shortcuts appear when long pressed
- UiObject2 icon = scrollAndFind(appsContainer, By.text(testApp.getLabel().toString()));
- // Press icon center until shortcuts appear
- Point iconCenter = icon.getVisibleCenter();
- TestViewHelpers.sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
- UiObject2 deepShortcutsContainer = TestViewHelpers.findViewById(
- R.id.deep_shortcuts_container);
- assertNotNull(deepShortcutsContainer);
- TestViewHelpers.sendPointer(MotionEvent.ACTION_UP, iconCenter);
+ executeOnLauncher(
+ launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+ isOptionsPopupVisible(launcher)));
- // Verify that launching a shortcut opens a page with the same text
- assertTrue(deepShortcutsContainer.getChildCount() > 0);
+ final AppIconMenuItem menuItem = menu.getMenuItem(1);
+ final String itemName = menuItem.getText();
- // Pick second children as it starts showing shortcuts.
- UiObject2 shortcut = deepShortcutsContainer.getChildren().get(1)
- .findObject(TestViewHelpers.getSelectorForId(R.id.bubble_text));
- shortcut.click();
+ menuItem.launch();
assertTrue(mDevice.wait(Until.hasObject(By.pkg(
testApp.getComponentName().getPackageName())
- .text(shortcut.getText())), DEFAULT_UI_TIMEOUT));
+ .text(itemName)), DEFAULT_UI_TIMEOUT));
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index b7ae9f1..1c0ecb9 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,6 +16,7 @@
package com.android.launcher3.tapl;
+import android.graphics.Point;
import android.widget.TextView;
import androidx.test.uiautomator.By;
@@ -56,4 +57,15 @@
UiObject2 getIcon() {
return mIcon;
}
+
+ /**
+ * Long-clicks the icon to open its menu.
+ */
+ public AppIconMenu openMenu() {
+ final Point iconCenter = mIcon.getVisibleCenter();
+ mLauncher.longTap(iconCenter.x, iconCenter.y);
+ final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject(
+ "deep_shortcuts_container");
+ return new AppIconMenu(mLauncher, deepShortcutsContainer);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
new file mode 100644
index 0000000..2a03f9a
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Context menu of an app icon.
+ */
+public class AppIconMenu {
+ private final LauncherInstrumentation mLauncher;
+ private final UiObject2 mDeepShortcutsContainer;
+
+ AppIconMenu(LauncherInstrumentation launcher,
+ UiObject2 deepShortcutsContainer) {
+ mLauncher = launcher;
+ mDeepShortcutsContainer = deepShortcutsContainer;
+ }
+
+ /**
+ * Returns a menu item with a given number. Fails if it doesn't exist.
+ */
+ public AppIconMenuItem getMenuItem(int itemNumber) {
+ assertTrue(mDeepShortcutsContainer.getChildCount() > itemNumber);
+
+ final UiObject2 shortcut = mLauncher.waitForObjectInContainer(
+ mDeepShortcutsContainer.getChildren().get(itemNumber), "bubble_text");
+ return new AppIconMenuItem(mLauncher, shortcut);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
new file mode 100644
index 0000000..7b2abeb
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+/**
+ * Menu item in an app icon menu.
+ */
+public class AppIconMenuItem {
+ private final LauncherInstrumentation mLauncher;
+ final UiObject2 mShortcut;
+
+ AppIconMenuItem(LauncherInstrumentation launcher,
+ UiObject2 shortcut) {
+ mLauncher = launcher;
+ mShortcut = shortcut;
+ }
+
+ /**
+ * Returns the visible text of the menu item.
+ */
+ public String getText() {
+ return mShortcut.getText();
+ }
+
+ /**
+ * Launches the action for the menu item.
+ */
+ public Background launch() {
+ assertTrue("Clicking a menu item didn't open a new window: " + mShortcut.getText(),
+ mShortcut.clickAndWait(Until.newWindow(), LauncherInstrumentation.WAIT_TIME_MS));
+ return new Background(mLauncher);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 31abc53..67106f7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -28,6 +28,13 @@
import android.view.Surface;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
import com.android.launcher3.TestProtocol;
import com.android.quickstep.SwipeUpSetting;
@@ -36,13 +43,6 @@
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeoutException;
-import androidx.annotation.NonNull;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
/**
* The main tapl object. The only object that can be explicitly constructed by the using code. It
* produces all other objects.
@@ -394,6 +394,11 @@
return mDevice;
}
+ void longTap(int x, int y) {
+ mDevice.drag(x, y, x, y, 0);
+ }
+
+
void swipe(int startX, int startY, int endX, int endY) {
executeAndWaitForEvent(
() -> mDevice.swipe(startX, startY, endX, endY, 60),