diff options
8 files changed, 299 insertions, 3 deletions
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 000000000000..835d0adbef58 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_chevron_up.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="@android:color/white" + android:pathData="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/> +</vector> 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 000000000000..b6bd3b32995e --- /dev/null +++ b/packages/SystemUI/res/layout/recents_swipe_up_onboarding.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="48dp" + android:layout_width="match_parent" + android:background="@android:color/black" + android:layout_gravity="center"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/recents_swipe_up_onboarding" + android:textColor="@android:color/white" + android:drawableBottom="@drawable/ic_chevron_up"/> + <ImageView + android:id="@+id/dismiss" + android:layout_width="48dp" + android:layout_height="48dp" + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:paddingEnd="18dp" + android:src="@drawable/ic_close_white" + android:layout_gravity="center_vertical|end"/> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6437903bd12a..0b2f2d27ec31 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -809,6 +809,8 @@ <string name="recents_stack_action_button_label">Clear all</string> <!-- Recents: Hint text that shows on the drop targets to start multiwindow. [CHAR LIMIT=NONE] --> <string name="recents_drag_hint_message">Drag here to use split screen</string> + <!-- Recents: Text that shows above the nav bar after launching a few apps. [CHAR LIMIT=NONE] --> + <string name="recents_swipe_up_onboarding">Swipe up to switch apps</string> <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] --> <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string> 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 1c99d3846d8e..c9a6ea9939f5 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<ActivityManager.RunningTaskInfo> 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 0be522bfb8cb..244c1b990448 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<OverviewProxyLis return mOverviewProxy; } + public ComponentName getLauncherComponent() { + return mLauncherComponentName; + } + private void disconnectFromLauncherService() { if (mOverviewProxy != null) { mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0); diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 4437d314a7c2..9319bc60f9ef 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -48,6 +48,8 @@ public final class Prefs { Key.QS_WORK_ADDED, Key.QS_NIGHTDISPLAY_ADDED, Key.SEEN_MULTI_USER, + Key.NUM_APPS_LAUNCHED, + Key.HAS_SWIPED_UP_FOR_RECENTS, }) public @interface Key { @Deprecated @@ -75,6 +77,8 @@ public final class Prefs { @Deprecated String QS_NIGHTDISPLAY_ADDED = "QsNightDisplayAdded"; String SEEN_MULTI_USER = "HasSeenMultiUser"; + String NUM_APPS_LAUNCHED = "NumAppsLaunched"; + String HAS_SWIPED_UP_FOR_RECENTS = "HasSwipedUpForRecents"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java new file mode 100644 index 000000000000..f065f48b20b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java @@ -0,0 +1,204 @@ +/* + * 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.systemui.recents; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Build; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +import com.android.systemui.Prefs; +import com.android.systemui.R; +import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; +import com.android.systemui.shared.system.ActivityManagerWrapper; + +/** + * Shows onboarding for the new recents interaction in P (codenamed quickstep). + */ +@TargetApi(Build.VERSION_CODES.P) +public class SwipeUpOnboarding { + + private static final String TAG = "SwipeUpOnboarding"; + private static final boolean RESET_PREFS_FOR_DEBUG = false; + private static final long SHOW_DELAY_MS = 500; + private static final long SHOW_HIDE_DURATION_MS = 300; + // Don't show the onboarding until the user has launched this number of apps. + private static final int SHOW_ON_APP_LAUNCH = 3; + + private final Context mContext; + private final WindowManager mWindowManager; + private final View mLayout; + + private boolean mTaskListenerRegistered; + private ComponentName mLauncherComponent; + private boolean mLayoutAttachedToWindow; + + private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() { + @Override + public void onTaskStackChanged() { + ActivityManager.RunningTaskInfo info = ActivityManagerWrapper.getInstance() + .getRunningTask(ACTIVITY_TYPE_UNDEFINED /* ignoreActivityType */); + int activityType = info.configuration.windowConfiguration.getActivityType(); + int numAppsLaunched = Prefs.getInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0); + if (activityType == ACTIVITY_TYPE_STANDARD) { + numAppsLaunched++; + if (numAppsLaunched >= 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 059ce929b290..4b20a896982a 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<Nav private NavigationBarInflaterView mNavigationInflaterView; private RecentsComponent mRecentsComponent; private Divider mDivider; + private SwipeUpOnboarding mSwipeUpOnboarding; private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; @@ -206,6 +208,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private final OverviewProxyListener mOverviewProxyListener = isConnected -> { setSlippery(!isConnected); setDisabledFlags(mDisabledFlags, true); + setUpSwipeUpOnboarding(isConnected); }; public NavigationBarView(Context context, AttributeSet attrs) { @@ -237,6 +240,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav new ButtonDispatcher(R.id.rotate_suggestion)); mOverviewProxyService = Dependency.get(OverviewProxyService.class); + mSwipeUpOnboarding = new SwipeUpOnboarding(context); } public BarTransitions getBarTransitions() { @@ -740,6 +744,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateTaskSwitchHelper(); updateIcons(getContext(), mConfiguration, newConfig); updateRecentsIcon(); + mSwipeUpOnboarding.onConfigurationChanged(newConfig); if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) { // If car mode or density changes, we need to reset the icons. @@ -829,6 +834,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav Dependency.get(PluginManager.class).addPluginListener(this, NavGesture.class, false /* Only one */); mOverviewProxyService.addCallback(mOverviewProxyListener); + setUpSwipeUpOnboarding(mOverviewProxyService.getProxy() != null); } @Override @@ -839,6 +845,15 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mGestureHelper.destroy(); } mOverviewProxyService.removeCallback(mOverviewProxyListener); + setUpSwipeUpOnboarding(false); + } + + private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) { + if (connectedToOverviewProxy) { + mSwipeUpOnboarding.onConnectedToLauncher(mOverviewProxyService.getLauncherComponent()); + } else { + mSwipeUpOnboarding.onDisconnectedFromLauncher(); + } } @Override |