diff --git a/packages/SystemUI/res/drawable/ic_chevron_up.xml b/packages/SystemUI/res/drawable/ic_chevron_up.xml
new file mode 100644
index 0000000000000..835d0adbef582
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_chevron_up.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml b/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml
new file mode 100644
index 0000000000000..b6bd3b32995e9
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6437903bd12a8..0b2f2d27ec311 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -809,6 +809,8 @@
Clear all
Drag here to use split screen
+
+ Swipe up to switch apps
Split Horizontal
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 1c99d3846d8ea..c9a6ea9939f56 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -30,6 +30,7 @@ import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IAssistDataReceiver;
+import android.app.WindowConfiguration.ActivityType;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -96,11 +97,14 @@ public class ActivityManagerWrapper {
* @return the top running task (can be {@code null}).
*/
public ActivityManager.RunningTaskInfo getRunningTask() {
+ return getRunningTask(ACTIVITY_TYPE_RECENTS /* ignoreActivityType */);
+ }
+
+ public ActivityManager.RunningTaskInfo getRunningTask(@ActivityType int ignoreActivityType) {
// Note: The set of running tasks from the system is ordered by recency
try {
List tasks =
- ActivityManager.getService().getFilteredTasks(1,
- ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
+ ActivityManager.getService().getFilteredTasks(1, ignoreActivityType,
WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
if (tasks.isEmpty()) {
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 0be522bfb8cbd..244c1b990448a 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -195,6 +195,10 @@ public class OverviewProxyService implements CallbackController= SHOW_ON_APP_LAUNCH) {
+ show();
+ } else {
+ Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, numAppsLaunched);
+ }
+ } else {
+ String runningPackage = info.topActivity.getPackageName();
+ // TODO: use callback from the overview proxy service to handle this case
+ if (runningPackage.equals(mLauncherComponent.getPackageName())
+ && activityType == ACTIVITY_TYPE_RECENTS) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true);
+ onDisconnectedFromLauncher();
+ } else {
+ hide(false);
+ }
+ }
+ }
+ };
+
+ private final View.OnAttachStateChangeListener mOnAttachStateChangeListener
+ = new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ if (view == mLayout) {
+ mLayoutAttachedToWindow = true;
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ if (view == mLayout) {
+ mLayoutAttachedToWindow = false;
+ }
+ }
+ };
+
+ public SwipeUpOnboarding(Context context) {
+ mContext = context;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_swipe_up_onboarding, null);
+ mLayout.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ mLayout.findViewById(R.id.dismiss).setOnClickListener(v -> hide(true));
+
+ if (RESET_PREFS_FOR_DEBUG) {
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
+ Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
+ }
+ }
+
+ public void onConnectedToLauncher(ComponentName launcherComponent) {
+ mLauncherComponent = launcherComponent;
+ boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
+ Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
+ if (!mTaskListenerRegistered && !alreadyLearnedSwipeUpForRecents) {
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
+ mTaskListenerRegistered = true;
+ }
+ }
+
+ public void onDisconnectedFromLauncher() {
+ if (mTaskListenerRegistered) {
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
+ mTaskListenerRegistered = false;
+ }
+ hide(false);
+ }
+
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ if (newConfiguration.orientation != Configuration.ORIENTATION_PORTRAIT) {
+ hide(false);
+ }
+ }
+
+ public void show() {
+ // Only show in portrait.
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
+ mLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ mWindowManager.addView(mLayout, getWindowLayoutParams());
+ int layoutHeight = mLayout.getHeight();
+ if (layoutHeight == 0) {
+ mLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ layoutHeight = mLayout.getMeasuredHeight();
+ }
+ mLayout.setTranslationY(layoutHeight);
+ mLayout.setAlpha(0);
+ mLayout.animate()
+ .translationY(0)
+ .alpha(1f)
+ .withLayer()
+ .setStartDelay(SHOW_DELAY_MS)
+ .setDuration(SHOW_HIDE_DURATION_MS)
+ .setInterpolator(new DecelerateInterpolator())
+ .start();
+ }
+ }
+
+ public void hide(boolean animate) {
+ if (mLayoutAttachedToWindow) {
+ if (animate) {
+ mLayout.animate()
+ .translationY(mLayout.getHeight())
+ .alpha(0f)
+ .withLayer()
+ .setDuration(SHOW_HIDE_DURATION_MS)
+ .setInterpolator(new AccelerateInterpolator())
+ .withEndAction(() -> mWindowManager.removeView(mLayout))
+ .start();
+ } else {
+ mWindowManager.removeView(mLayout);
+ }
+ }
+ }
+
+ private WindowManager.LayoutParams getWindowLayoutParams() {
+ int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
+ flags,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("SwipeUpOnboarding");
+ lp.gravity = Gravity.BOTTOM;
+ return lp;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 059ce929b290f..4b20a896982a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -57,10 +57,11 @@ import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.recents.SwipeUpOnboarding;
import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -124,6 +125,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener