diff --git a/quickstep/res/drawable/ic_floating_task_button.xml b/quickstep/res/drawable/ic_floating_task_button.xml new file mode 100644 index 0000000000..e50f65cee7 --- /dev/null +++ b/quickstep/res/drawable/ic_floating_task_button.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java new file mode 100644 index 0000000000..c62493c828 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/FloatingTaskIntentResolver.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 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.taskbar; + +import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.text.TextUtils; +import android.util.Log; + +import com.android.launcher3.R; + +// TODO: This would be replaced by the thing that has the role and provides the intent. +/** + * Helper to determine what intent should be used to display in a floating window, if one + * exists. + */ +public class FloatingTaskIntentResolver { + private static final String TAG = FloatingTaskIntentResolver.class.getSimpleName(); + + @Nullable + /** Gets an intent for a floating task, if one exists. */ + public static Intent getIntent(Context context) { + PackageManager pm = context.getPackageManager(); + String pkg = context.getString(R.string.floating_task_package); + String action = context.getString(R.string.floating_task_action); + if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(action)) { + Log.d(TAG, "intent could not be found, pkg= " + pkg + " action= " + action); + return null; + } + Intent intent = createIntent(pm, null, pkg, action); + if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + Log.d(TAG, "No valid intent found!"); + return null; + } + + @Nullable + private static Intent createIntent(PackageManager pm, @Nullable String activityName, + String packageName, String action) { + if (TextUtils.isEmpty(activityName)) { + activityName = queryActivityForAction(pm, packageName, action); + } + if (TextUtils.isEmpty(activityName)) { + Log.d(TAG, "Activity name is empty even after action search: " + action); + return null; + } + ComponentName component = new ComponentName(packageName, activityName); + Intent intent = new Intent(action).setComponent(component); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Log.d(TAG, "createIntent returning: " + intent); + return intent; + } + + @Nullable + private static String queryActivityForAction(PackageManager pm, String packageName, + String action) { + Intent intent = new Intent(action).setPackage(packageName); + ResolveInfo resolveInfo = pm.resolveActivity(intent, MATCH_DEFAULT_ONLY); + if (resolveInfo == null || resolveInfo.activityInfo == null) { + Log.d(TAG, "queryActivityForAction: + " + resolveInfo); + return null; + } + ActivityInfo info = resolveInfo.activityInfo; + if (!info.exported) { + Log.d(TAG, "queryActivityForAction: + " + info + " not exported"); + return null; + } + if (!info.enabled) { + Log.d(TAG, "queryActivityForAction: + " + info + " not enabled"); + return null; + } + return resolveInfo.activityInfo.name; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java new file mode 100644 index 0000000000..b15669b5ef --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/LaunchFloatingTaskButton.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 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.taskbar; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.icons.FastBitmapDrawable; + +/** + * Button in Taskbar that opens something in a floating task. + */ +public class LaunchFloatingTaskButton extends BubbleTextView { + + public LaunchFloatingTaskButton(Context context) { + this(context, null); + } + + public LaunchFloatingTaskButton(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LaunchFloatingTaskButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + Context theme = new ContextThemeWrapper(context, R.style.AllAppsButtonTheme); + Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory() + .createScaledBitmapWithShadow( + theme.getDrawable(R.drawable.ic_floating_task_button)); + setIcon(new FastBitmapDrawable(bitmap)); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index dbf9759167..bb82d19734 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -16,10 +16,13 @@ package com.android.launcher3.taskbar; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.SystemProperties; import android.util.AttributeSet; +import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -53,6 +56,7 @@ import java.util.function.Predicate; * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. */ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable { + private static final String TAG = TaskbarView.class.getSimpleName(); private static final float TASKBAR_BACKGROUND_LUMINANCE = 0.30f; public int mThemeIconsBackground; @@ -81,6 +85,12 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar private View mQsb; + // Only non-null when device supports having a floating task. + private @Nullable BubbleTextView mFloatingTaskButton; + private @Nullable Intent mFloatingTaskIntent; + private static final boolean FLOATING_TASKS_ENABLED = + SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false); + public TaskbarView(@NonNull Context context) { this(context, null); } @@ -123,6 +133,19 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar // TODO: Disable touch events on QSB otherwise it can crash. mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false); + + if (FLOATING_TASKS_ENABLED) { + mFloatingTaskIntent = FloatingTaskIntentResolver.getIntent(context); + if (mFloatingTaskIntent != null) { + mFloatingTaskButton = new LaunchFloatingTaskButton(context); + mFloatingTaskButton.setLayoutParams( + new ViewGroup.LayoutParams(mIconTouchSize, mIconTouchSize)); + mFloatingTaskButton.setPadding(mItemPadding, mItemPadding, mItemPadding, + mItemPadding); + } else { + Log.d(TAG, "Floating tasks is enabled but no intent was found!"); + } + } } private int getColorWithGivenLuminance(int color, float luminance) { @@ -150,6 +173,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar if (mAllAppsButton != null) { mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener()); } + if (mFloatingTaskButton != null) { + mFloatingTaskButton.setOnClickListener( + mControllerCallbacks.getFloatingTaskButtonListener(mFloatingTaskIntent)); + } } private void removeAndRecycle(View view) { @@ -174,6 +201,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar } removeView(mQsb); + if (mFloatingTaskButton != null) { + removeView(mFloatingTaskButton); + } + for (int i = 0; i < hotseatItemInfos.length; i++) { ItemInfo hotseatItemInfo = hotseatItemInfos[i]; if (hotseatItemInfo == null) { @@ -255,6 +286,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar mQsb.setVisibility(View.INVISIBLE); } + if (mFloatingTaskButton != null) { + int index = Utilities.isRtl(getResources()) ? 0 : getChildCount(); + addView(mFloatingTaskButton, index); + } + mThemeIconsBackground = calculateThemeIconsBackground(); setThemedIconsBackgroundColor(mThemeIconsBackground); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 992aa4bd7d..00d5083dd3 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -24,6 +24,7 @@ import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode; import static com.android.quickstep.AnimatedFloat.VALUE; import android.annotation.NonNull; +import android.content.Intent; import android.graphics.Rect; import android.util.FloatProperty; import android.util.Log; @@ -51,6 +52,7 @@ import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.MultiValueAlpha; import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.SystemUiProxy; import java.io.PrintWriter; import java.util.function.Predicate; @@ -427,6 +429,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar }; } + public View.OnClickListener getFloatingTaskButtonListener(@NonNull Intent intent) { + return v -> { + SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(v.getContext()); + proxy.showFloatingTask(intent); + }; + } + public View.OnLongClickListener getIconOnLongClickListener() { return mControllers.taskbarDragController::startDragOnLongClick; } diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 944e2f985d..86e8afa0ad 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -57,6 +57,7 @@ import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationCon import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController; import com.android.systemui.shared.system.smartspace.SmartspaceState; import com.android.wm.shell.back.IBackAnimation; +import com.android.wm.shell.floating.IFloatingTasks; import com.android.wm.shell.onehanded.IOneHanded; import com.android.wm.shell.pip.IPip; import com.android.wm.shell.pip.IPipAnimationListener; @@ -87,6 +88,7 @@ public class SystemUiProxy implements ISystemUiProxy { private IPip mPip; private ISysuiUnlockAnimationController mSysuiUnlockAnimationController; private ISplitScreen mSplitScreen; + private IFloatingTasks mFloatingTasks; private IOneHanded mOneHanded; private IShellTransitions mShellTransitions; private IStartingWindow mStartingWindow; @@ -163,7 +165,7 @@ public class SystemUiProxy implements ISystemUiProxy { } public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen, - IOneHanded oneHanded, IShellTransitions shellTransitions, + IFloatingTasks floatingTasks, IOneHanded oneHanded, IShellTransitions shellTransitions, IStartingWindow startingWindow, IRecentTasks recentTasks, ISysuiUnlockAnimationController sysuiUnlockAnimationController, IBackAnimation backAnimation) { @@ -171,6 +173,7 @@ public class SystemUiProxy implements ISystemUiProxy { mSystemUiProxy = proxy; mPip = pip; mSplitScreen = splitScreen; + mFloatingTasks = floatingTasks; mOneHanded = oneHanded; mShellTransitions = shellTransitions; mStartingWindow = startingWindow; @@ -203,7 +206,7 @@ public class SystemUiProxy implements ISystemUiProxy { } public void clearProxy() { - setProxy(null, null, null, null, null, null, null, null, null); + setProxy(null, null, null, null, null, null, null, null, null, null); } // TODO(141886704): Find a way to remove this @@ -657,6 +660,20 @@ public class SystemUiProxy implements ISystemUiProxy { return null; } + // + // Floating tasks + // + + public void showFloatingTask(Intent intent) { + if (mFloatingTasks != null) { + try { + mFloatingTasks.showTask(intent); + } catch (RemoteException e) { + Log.w(TAG, "Launcher: Failed call showFloatingTask", e); + } + } + } + // // One handed // diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 4923948180..e207a1b73b 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -28,6 +28,7 @@ import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; @@ -110,6 +111,7 @@ import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController; import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.wm.shell.back.IBackAnimation; +import com.android.wm.shell.floating.IFloatingTasks; import com.android.wm.shell.onehanded.IOneHanded; import com.android.wm.shell.pip.IPip; import com.android.wm.shell.recents.IRecentTasks; @@ -167,6 +169,8 @@ public class TouchInteractionService extends Service IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP)); ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder( KEY_EXTRA_SHELL_SPLIT_SCREEN)); + IFloatingTasks floatingTasks = IFloatingTasks.Stub.asInterface(bundle.getBinder( + KEY_EXTRA_SHELL_FLOATING_TASKS)); IOneHanded onehanded = IOneHanded.Stub.asInterface( bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED)); IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface( @@ -182,8 +186,8 @@ public class TouchInteractionService extends Service bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION)); MAIN_EXECUTOR.execute(() -> { SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip, - splitscreen, onehanded, shellTransitions, startingWindow, recentTasks, - launcherUnlockAnimationController, backAnimation); + splitscreen, floatingTasks, onehanded, shellTransitions, startingWindow, + recentTasks, launcherUnlockAnimationController, backAnimation); TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()"); preloadOverview(true /* fromInit */); }); diff --git a/res/values/config.xml b/res/values/config.xml index d3f5033f0b..11b6e8ca45 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -204,4 +204,7 @@ 0 + + +