Merge "Allows a plugin Settings activity to designate plugins it supports." into sc-dev
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index 5f1046d..b124b33 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -24,6 +24,8 @@
android:id="@+id/taskbar_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@color/taskbar_background"/>
+ android:background="@color/taskbar_background"
+ android:gravity="center"
+ android:animateLayoutChanges="true"/>
</com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_app_icon.xml b/quickstep/res/layout/taskbar_app_icon.xml
new file mode 100644
index 0000000..6fefdb6
--- /dev/null
+++ b/quickstep/res/layout/taskbar_app_icon.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<com.android.launcher3.views.DoubleShadowBubbleTextView style="@style/BaseIcon.Workspace.Taskbar" />
diff --git a/quickstep/res/layout/taskbar_predicted_app_icon.xml b/quickstep/res/layout/taskbar_predicted_app_icon.xml
new file mode 100644
index 0000000..211ebc8
--- /dev/null
+++ b/quickstep/res/layout/taskbar_predicted_app_icon.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<com.android.launcher3.uioverrides.PredictedAppIcon style="@style/BaseIcon.Workspace.Taskbar" />
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 4272f50..39cc0b8 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -122,4 +122,9 @@
<!-- Taskbar -->
<dimen name="taskbar_size">48dp</dimen>
+ <dimen name="taskbar_icon_size">32dp</dimen>
+ <dimen name="taskbar_icon_touch_size">48dp</dimen>
+ <dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
+ <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
+ <dimen name="taskbar_icon_spacing">14dp</dimen>
</resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 8d054b4..5a353f0 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -85,4 +85,10 @@
<item name="android:drawablePadding">8dp</item>
<item name="android:textAllCaps">false</item>
</style>
+
+ <!-- Icon displayed on the taskbar -->
+ <style name="BaseIcon.Workspace.Taskbar" >
+ <item name="iconDisplay">taskbar</item>
+ <item name="iconSizeOverride">@dimen/taskbar_icon_size</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 09a3cfd..cbe0eb0 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -265,6 +265,11 @@
}
@Override
+ public boolean isViewInTaskbar(View v) {
+ return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v);
+ }
+
+ @Override
public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
QuickstepAppTransitionManagerImpl appTransitionManager =
(QuickstepAppTransitionManagerImpl) getAppTransitionManager();
@@ -350,6 +355,10 @@
// populating workspace.
// TODO: Find a better place for this
WellbeingModel.INSTANCE.get(this);
+
+ if (mTaskbarController != null) {
+ mTaskbarController.onHotseatUpdated();
+ }
}
@Override
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 36c8bb8..876cabc 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -202,9 +202,10 @@
}
@Override
- public boolean supportsAdaptiveIconAnimation() {
+ public boolean supportsAdaptiveIconAnimation(View clickedView) {
return hasControlRemoteAppTransitionPermission()
- && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get();
+ && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
+ && !mLauncher.isViewInTaskbar(clickedView);
}
/**
@@ -972,9 +973,12 @@
launcherIsATargetWithMode(appTargets, MODE_CLOSING);
final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets);
+ final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV);
if (launchingFromRecents) {
composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
launcherClosing);
+ } else if (launchingFromTaskbar) {
+ // TODO
} else {
composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
launcherClosing);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index b2de4c9..aa6601b 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -37,7 +37,6 @@
import com.android.launcher3.DropTarget;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -75,7 +74,7 @@
private int mHotSeatItemsCount;
- private Launcher mLauncher;
+ private QuickstepLauncher mLauncher;
private final Hotseat mHotseat;
private List<ItemInfo> mPredictedItems = Collections.emptyList();
@@ -108,7 +107,7 @@
return true;
};
- public HotseatPredictionController(Launcher launcher) {
+ public HotseatPredictionController(QuickstepLauncher launcher) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
@@ -229,6 +228,10 @@
} else {
if (callback != null) callback.run();
}
+
+ if (mLauncher.getTaskbarController() != null) {
+ mLauncher.getTaskbarController().onHotseatUpdated();
+ }
}
/**
@@ -242,6 +245,10 @@
* start and pauses predicted apps update on the hotseat
*/
public void setPauseUIUpdate(boolean paused) {
+ if (mLauncher.getTaskbarController() != null) {
+ // Taskbar is present, always allow updates since hotseat is still visible.
+ return;
+ }
mUIUpdatePaused = paused;
if (!paused) {
fillGapsWithPrediction();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index bdf7f8a..7608645 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -26,6 +26,8 @@
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowManager;
import androidx.annotation.Nullable;
@@ -36,7 +38,9 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.ItemClickHandler;
import com.android.quickstep.AnimatedFloat;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -55,6 +59,8 @@
private final Point mTaskbarSize;
private final TaskbarStateHandler mTaskbarStateHandler;
private final TaskbarVisibilityController mTaskbarVisibilityController;
+ private final TaskbarHotseatController mHotseatController;
+ private final TaskbarDragController mDragController;
// Initialized in init().
private WindowManager.LayoutParams mWindowLayoutParams;
@@ -64,12 +70,16 @@
mLauncher = launcher;
mTaskbarContainerView = taskbarContainerView;
mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
+ mTaskbarView.setCallbacks(createTaskbarViewCallbacks());
mWindowManager = mLauncher.getWindowManager();
mTaskbarSize = new Point(MATCH_PARENT,
mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size));
mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher,
createTaskbarVisibilityControllerCallbacks());
+ mHotseatController = new TaskbarHotseatController(mLauncher,
+ createTaskbarHotseatControllerCallbacks());
+ mDragController = new TaskbarDragController(mLauncher);
}
private TaskbarVisibilityControllerCallbacks createTaskbarVisibilityControllerCallbacks() {
@@ -87,13 +97,38 @@
};
}
+ private TaskbarViewCallbacks createTaskbarViewCallbacks() {
+ return new TaskbarViewCallbacks() {
+ @Override
+ public View.OnClickListener getItemOnClickListener() {
+ return ItemClickHandler.INSTANCE;
+ }
+
+ @Override
+ public View.OnLongClickListener getItemOnLongClickListener() {
+ return mDragController::startDragOnLongClick;
+ }
+ };
+ }
+
+ private TaskbarHotseatControllerCallbacks createTaskbarHotseatControllerCallbacks() {
+ return new TaskbarHotseatControllerCallbacks() {
+ @Override
+ public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+ mTaskbarView.updateHotseatItems(hotseatItemInfos);
+ }
+ };
+ }
+
/**
* Initializes the Taskbar, including adding it to the screen.
*/
public void init() {
+ mTaskbarView.init(mHotseatController.getNumHotseatIcons());
addToWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
mTaskbarVisibilityController.init();
+ mHotseatController.init();
}
private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
@@ -109,9 +144,11 @@
* Removes the Taskbar from the screen, and removes any obsolete listeners etc.
*/
public void cleanup() {
+ mTaskbarView.cleanup();
removeFromWindowManager();
mTaskbarStateHandler.setTaskbarCallbacks(null);
mTaskbarVisibilityController.cleanup();
+ mHotseatController.cleanup();
}
private void removeFromWindowManager() {
@@ -191,6 +228,32 @@
}
/**
+ * Should be called when one or more items in the Hotseat have changed.
+ */
+ public void onHotseatUpdated() {
+ mHotseatController.onHotseatUpdated();
+ }
+
+ /**
+ * @param ev MotionEvent in screen coordinates.
+ * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
+ */
+ public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
+ return mTaskbarView.isEventOverAnyItem(ev);
+ }
+
+ public boolean isDraggingItem() {
+ return mTaskbarView.isDraggingItem();
+ }
+
+ /**
+ * @return Whether the given View is in the same window as Taskbar.
+ */
+ public boolean isViewInTaskbar(View v) {
+ return mTaskbarContainerView.getWindowId().equals(v.getWindowId());
+ }
+
+ /**
* Contains methods that TaskbarStateHandler can call to interface with TaskbarController.
*/
protected interface TaskbarStateHandlerCallbacks {
@@ -205,4 +268,19 @@
void updateTaskbarBackgroundAlpha(float alpha);
void updateTaskbarVisibilityAlpha(float alpha);
}
+
+ /**
+ * Contains methods that TaskbarView can call to interface with TaskbarController.
+ */
+ protected interface TaskbarViewCallbacks {
+ View.OnClickListener getItemOnClickListener();
+ View.OnLongClickListener getItemOnLongClickListener();
+ }
+
+ /**
+ * Contains methods that TaskbarHotseatController can call to interface with TaskbarController.
+ */
+ protected interface TaskbarHotseatControllerCallbacks {
+ void updateHotseatItems(ItemInfo[] hotseatItemInfos);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
new file mode 100644
index 0000000..2318ff9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2021 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.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.view.DragEvent;
+import android.view.View;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.systemui.shared.system.ClipDescriptionCompat;
+import com.android.systemui.shared.system.LauncherAppsCompat;
+
+/**
+ * Handles long click on Taskbar items to start a system drag and drop operation.
+ */
+public class TaskbarDragController {
+
+ private final BaseQuickstepLauncher mLauncher;
+ private final int mDragIconSize;
+
+ public TaskbarDragController(BaseQuickstepLauncher launcher) {
+ mLauncher = launcher;
+ Resources resources = mLauncher.getResources();
+ mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
+ }
+
+ /**
+ * Attempts to start a system drag and drop operation for the given View, using its tag to
+ * generate the ClipDescription and Intent.
+ * @return Whether {@link View#startDragAndDrop} started successfully.
+ */
+ protected boolean startDragOnLongClick(View view) {
+ if (!(view instanceof BubbleTextView)) {
+ return false;
+ }
+
+ BubbleTextView btv = (BubbleTextView) view;
+
+ View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
+ @Override
+ public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+ shadowSize.set(mDragIconSize, mDragIconSize);
+ // TODO: should be based on last touch point on the icon.
+ shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
+ }
+
+ @Override
+ public void onDrawShadow(Canvas canvas) {
+ canvas.save();
+ float scale = (float) mDragIconSize / btv.getIconSize();
+ canvas.scale(scale, scale);
+ btv.getIcon().draw(canvas);
+ canvas.restore();
+ }
+ };
+
+ Object tag = view.getTag();
+ ClipDescription clipDescription = null;
+ Intent intent = null;
+ if (tag instanceof WorkspaceItemInfo) {
+ WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
+ LauncherApps launcherApps = mLauncher.getSystemService(LauncherApps.class);
+ clipDescription = new ClipDescription(item.title,
+ new String[] {
+ item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ ? ClipDescriptionCompat.MIMETYPE_APPLICATION_SHORTCUT
+ : ClipDescriptionCompat.MIMETYPE_APPLICATION_ACTIVITY
+ });
+ intent = new Intent();
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ID, item.getDeepShortcutId());
+ } else {
+ intent.putExtra(ClipDescriptionCompat.EXTRA_PENDING_INTENT,
+ LauncherAppsCompat.getMainActivityLaunchIntent(launcherApps,
+ item.getIntent().getComponent(), null, item.user));
+ }
+ intent.putExtra(Intent.EXTRA_USER, item.user);
+ }
+
+ if (clipDescription != null && intent != null) {
+ ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
+ view.setOnDragListener(getDraggedViewDragListener());
+ return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE);
+ }
+ return false;
+ }
+
+ /**
+ * Hide the original Taskbar item while it is being dragged.
+ */
+ private View.OnDragListener getDraggedViewDragListener() {
+ return (view, dragEvent) -> {
+ switch (dragEvent.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ view.setVisibility(INVISIBLE);
+ return true;
+ case DragEvent.ACTION_DRAG_ENDED:
+ view.setVisibility(VISIBLE);
+ view.setOnDragListener(null);
+ return true;
+ }
+ return false;
+ };
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
new file mode 100644
index 0000000..4dc051a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.view.View;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.data.ItemInfo;
+
+/**
+ * Works with TaskbarController to update the TaskbarView's Hotseat items.
+ */
+public class TaskbarHotseatController {
+
+ private final BaseQuickstepLauncher mLauncher;
+ private final Hotseat mHotseat;
+ private final TaskbarController.TaskbarHotseatControllerCallbacks mTaskbarCallbacks;
+ private final int mNumHotseatIcons;
+
+ private final DragController.DragListener mDragListener = new DragController.DragListener() {
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ }
+
+ @Override
+ public void onDragEnd() {
+ onHotseatUpdated();
+ }
+ };
+
+ public TaskbarHotseatController(BaseQuickstepLauncher launcher,
+ TaskbarController.TaskbarHotseatControllerCallbacks taskbarCallbacks) {
+ mLauncher = launcher;
+ mHotseat = mLauncher.getHotseat();
+ mTaskbarCallbacks = taskbarCallbacks;
+ mNumHotseatIcons = mLauncher.getDeviceProfile().inv.numHotseatIcons;
+ }
+
+ protected void init() {
+ mLauncher.getDragController().addDragListener(mDragListener);
+ }
+
+ protected void cleanup() {
+ mLauncher.getDragController().removeDragListener(mDragListener);
+ }
+
+ /**
+ * Called when any Hotseat item changes, and reports the new list of items to TaskbarController.
+ */
+ protected void onHotseatUpdated() {
+ ShortcutAndWidgetContainer shortcutsAndWidgets = mHotseat.getShortcutsAndWidgets();
+ ItemInfo[] hotseatItemInfos = new ItemInfo[mNumHotseatIcons];
+ for (int i = 0; i < shortcutsAndWidgets.getChildCount(); i++) {
+ View child = shortcutsAndWidgets.getChildAt(i);
+ Object tag = shortcutsAndWidgets.getChildAt(i).getTag();
+ if (tag instanceof ItemInfo) {
+ ItemInfo itemInfo = (ItemInfo) tag;
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ // Since the hotseat might be laid out vertically or horizontally, use whichever
+ // index is higher.
+ hotseatItemInfos[Math.max(lp.cellX, lp.cellY)] = itemInfo;
+ }
+ }
+
+ mTaskbarCallbacks.updateHotseatItems(hotseatItemInfos);
+ }
+
+ protected int getNumHotseatIcons() {
+ return mNumHotseatIcons;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index bf6e946..c98f09c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -16,19 +16,50 @@
package com.android.launcher3.taskbar;
import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
import android.widget.LinearLayout;
+import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
public class TaskbarView extends LinearLayout {
private final ColorDrawable mBackgroundDrawable;
+ private final int mItemMarginLeftRight;
+ private final int mIconTouchSize;
+ private final int mTouchSlop;
+ private final RectF mTempDelegateBounds = new RectF();
+ private final RectF mDelegateSlopBounds = new RectF();
+ private final int[] mTempOutLocation = new int[2];
+
+ // Initialized in init().
+ private int mHotseatStartIndex;
+ private int mHotseatEndIndex;
+
+ private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+
+ // Delegate touches to the closest view if within mIconTouchSize.
+ private boolean mDelegateTargeted;
+ private View mDelegateView;
+
+ private boolean mIsDraggingItem;
public TaskbarView(@NonNull Context context) {
this(context, null);
@@ -46,7 +77,26 @@
public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+
+ Resources resources = getResources();
mBackgroundDrawable = (ColorDrawable) getBackground();
+ mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+ mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ }
+
+ protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
+ mControllerCallbacks = taskbarViewCallbacks;
+ }
+
+ protected void init(int numHotseatIcons) {
+ mHotseatStartIndex = 0;
+ mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
+ updateHotseatItems(new ItemInfo[numHotseatIcons]);
+ }
+
+ protected void cleanup() {
+ removeAllViews();
}
/**
@@ -56,4 +106,159 @@
public void setBackgroundAlpha(float alpha) {
mBackgroundDrawable.setAlpha((int) (alpha * 255));
}
+
+ /**
+ * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
+ */
+ protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+ for (int i = 0; i < hotseatItemInfos.length; i++) {
+ ItemInfo hotseatItemInfo = hotseatItemInfos[i];
+ int hotseatIndex = mHotseatStartIndex + i;
+ View hotseatView = getChildAt(hotseatIndex);
+
+ // Replace any Hotseat views with the appropriate type if it's not already that type.
+ final int expectedLayoutResId;
+ if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
+ expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
+ } else {
+ expectedLayoutResId = R.layout.taskbar_app_icon;
+ }
+ if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) {
+ removeView(hotseatView);
+ BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId);
+ LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize());
+ lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
+ hotseatView = btv;
+ addView(hotseatView, hotseatIndex, lp);
+ }
+
+ // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
+ if (hotseatView instanceof BubbleTextView
+ && hotseatItemInfo instanceof WorkspaceItemInfo) {
+ ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
+ (WorkspaceItemInfo) hotseatItemInfo);
+ hotseatView.setVisibility(VISIBLE);
+ hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+ hotseatView.setOnLongClickListener(
+ mControllerCallbacks.getItemOnLongClickListener());
+ } else {
+ hotseatView.setVisibility(GONE);
+ hotseatView.setOnClickListener(null);
+ hotseatView.setOnLongClickListener(null);
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean handled = delegateTouchIfNecessary(event);
+ return super.onTouchEvent(event) || handled;
+ }
+
+ /**
+ * User touched the Taskbar background. Determine whether the touch is close enough to a view
+ * that we should forward the touches to it.
+ * @return Whether a delegate view was chosen and it handled the touch event.
+ */
+ private boolean delegateTouchIfNecessary(MotionEvent event) {
+ final float x = event.getX();
+ final float y = event.getY();
+ if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) {
+ View delegateView = findDelegateView(x, y);
+ if (delegateView != null) {
+ mDelegateTargeted = true;
+ mDelegateView = delegateView;
+ mDelegateSlopBounds.set(mTempDelegateBounds);
+ mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop);
+ }
+ }
+
+ boolean sendToDelegate = mDelegateTargeted;
+ boolean inBounds = true;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_MOVE:
+ inBounds = mDelegateSlopBounds.contains(x, y);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mDelegateTargeted = false;
+ break;
+ }
+
+ boolean handled = false;
+ if (sendToDelegate) {
+ if (inBounds) {
+ // Offset event coordinates to be inside the target view
+ event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f);
+ } else {
+ // Offset event coordinates to be outside the target view (in case it does
+ // something like tracking pressed state)
+ event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2);
+ }
+ handled = mDelegateView.dispatchTouchEvent(event);
+ // Cleanup if this was the last event to send to the delegate.
+ if (!mDelegateTargeted) {
+ mDelegateView = null;
+ }
+ }
+ return handled;
+ }
+
+ /**
+ * Return an item whose touch bounds contain the given coordinates,
+ * or null if no such item exists.
+ *
+ * Also sets {@link #mTempDelegateBounds} to be the touch bounds of the chosen delegate view.
+ */
+ private @Nullable View findDelegateView(float x, float y) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (!child.isShown() || !child.isClickable()) {
+ continue;
+ }
+ int childCenterX = child.getLeft() + child.getWidth() / 2;
+ int childCenterY = child.getTop() + child.getHeight() / 2;
+ mTempDelegateBounds.set(
+ childCenterX - mIconTouchSize / 2f,
+ childCenterY - mIconTouchSize / 2f,
+ childCenterX + mIconTouchSize / 2f,
+ childCenterY + mIconTouchSize / 2f);
+ if (mTempDelegateBounds.contains(x, y)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
+ * touch bounds.
+ */
+ public boolean isEventOverAnyItem(MotionEvent ev) {
+ getLocationOnScreen(mTempOutLocation);
+ float xInOurCoordinates = ev.getX() - mTempOutLocation[0];
+ float yInOurCoorindates = ev.getY() - mTempOutLocation[1];
+ return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
+ }
+
+ @Override
+ public boolean onDragEvent(DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ mIsDraggingItem = true;
+ return true;
+ case DragEvent.ACTION_DRAG_ENDED:
+ mIsDraggingItem = false;
+ break;
+ }
+ return super.onDragEvent(event);
+ }
+
+ public boolean isDraggingItem() {
+ return mIsDraggingItem;
+ }
+
+ private View inflate(@LayoutRes int layoutResId) {
+ return LayoutInflater.from(getContext()).inflate(layoutResId, this, false);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 597c17b..22c4a7e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -184,7 +184,10 @@
}
private int getOutlineOffsetY() {
- return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+ if (mDisplay != DISPLAY_TASKBAR) {
+ return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+ }
+ return (getMeasuredHeight() / 2) - mNormalizedIconRadius;
}
private void drawEffect(Canvas canvas, boolean isBadged) {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 5f6e59f..ce14197 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -153,6 +153,13 @@
return deviceState.isInDeferredGestureRegion(ev);
}
+ /**
+ * @return Whether the gesture in progress should be cancelled.
+ */
+ public boolean shouldCancelCurrentGesture() {
+ return false;
+ }
+
public abstract void onExitOverview(RotationTouchHelper deviceState,
Runnable exitRunnable);
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index deb86ec..8b0d782 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -26,6 +26,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.Log;
+import android.view.MotionEvent;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -311,4 +312,22 @@
boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
taskbarController.setIsImeVisible(isImeVisible);
}
+
+ @Override
+ public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+ TaskbarController taskbarController = getTaskbarController();
+ if (taskbarController == null) {
+ return super.deferStartingActivity(deviceState, ev);
+ }
+ return taskbarController.isEventOverAnyTaskbarItem(ev);
+ }
+
+ @Override
+ public boolean shouldCancelCurrentGesture() {
+ TaskbarController taskbarController = getTaskbarController();
+ if (taskbarController == null) {
+ return super.shouldCancelCurrentGesture();
+ }
+ return taskbarController.isDraggingItem();
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index a98fc1c..8ebea33 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -514,9 +514,14 @@
}
}
- boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL)
+ boolean cancelGesture = mGestureState.getActivityInterface() != null
+ && mGestureState.getActivityInterface().shouldCancelCurrentGesture();
+ boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL || cancelGesture)
&& mConsumer != null
&& !mConsumer.getActiveConsumerInHierarchy().isConsumerDetachedFromGesture();
+ if (cancelGesture) {
+ event.setAction(ACTION_CANCEL);
+ }
mUncheckedConsumer.onMotionEvent(event);
if (cleanUpConsumer) {
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 96c30b5..e593fb4 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -57,6 +57,7 @@
<enum name="widget_section" value="3" />
<enum name="shortcut_popup" value="4" />
<enum name="hero_app" value="5" />
+ <enum name="taskbar" value="6" />
</attr>
<attr name="centerVertically" format="boolean" />
</declare-styleable>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3425f91..447c9ac 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -232,6 +232,8 @@
<string name="abandoned_promise_explanation">The app for this icon isn\'t installed.
You can remove it, or search for the app and install it manually.
</string>
+ <!-- Title for an app which is being installed. -->
+ <string name="app_installing_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> installing, <xliff:g id="progress" example="30%">%2$s</xliff:g> complete</string>
<!-- Title for an app which is being downloaded. -->
<string name="app_downloading_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> downloading, <xliff:g id="progress" example="30%">%2$s</xliff:g> complete</string>
<!-- Title for an app whose download has been started. -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 575d6cd..140340a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -92,6 +92,7 @@
private static final int DISPLAY_ALL_APPS = 1;
private static final int DISPLAY_FOLDER = 2;
private static final int DISPLAY_HERO_APP = 5;
+ protected static final int DISPLAY_TASKBAR = 6;
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
private static final float HIGHLIGHT_SCALE = 1.16f;
@@ -140,7 +141,7 @@
private Drawable mIcon;
private boolean mCenterVertically;
- private final int mDisplay;
+ protected final int mDisplay;
private final CheckLongPressHelper mLongPressHelper;
@@ -206,6 +207,8 @@
defaultIconSize = grid.folderChildIconSizePx;
} else if (mDisplay == DISPLAY_HERO_APP) {
defaultIconSize = grid.allAppsIconSizePx;
+ } else if (mDisplay == DISPLAY_TASKBAR) {
+ defaultIconSize = grid.iconSizePx;
} else {
// widget_selection or shortcut_popup
defaultIconSize = grid.iconSizePx;
@@ -289,6 +292,7 @@
setTag(info);
applyLoadingState(promiseStateChanged);
applyDotState(info, false /* animate */);
+ setDownloadStateContentDescription(info, info.getProgressLevel());
}
public void applyFromApplicationInfo(AppInfo info) {
@@ -304,6 +308,7 @@
applyProgressLevel();
}
applyDotState(info, false /* animate */);
+ setDownloadStateContentDescription(info, info.getProgressLevel());
}
/**
@@ -316,6 +321,8 @@
// Verify high res immediately
verifyHighRes();
+
+ setDownloadStateContentDescription(info, info.getProgressLevel());
}
/**
@@ -483,6 +490,10 @@
* @param canvas The canvas to draw to.
*/
protected void drawDotIfNecessary(Canvas canvas) {
+ if (mDisplay == DISPLAY_TASKBAR) {
+ // TODO: support notification dots in Taskbar
+ return;
+ }
if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
getIconBounds(mDotParams.iconBounds);
Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
@@ -635,9 +646,7 @@
setContentDescription(info.contentDescription != null
? info.contentDescription : "");
} else if (progressLevel > 0) {
- setContentDescription(getContext()
- .getString(R.string.app_downloading_title, info.title,
- NumberFormat.getPercentInstance().format(progressLevel * 0.01)));
+ setDownloadStateContentDescription(info, progressLevel);
} else {
setContentDescription(getContext()
.getString(R.string.app_waiting_download_title, info.title));
@@ -713,6 +722,24 @@
}
}
+ private void setDownloadStateContentDescription(ItemInfoWithIcon info, int progressLevel) {
+ if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
+ != 0) {
+ String percentageString = NumberFormat.getPercentInstance()
+ .format(progressLevel * 0.01);
+ if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
+ setContentDescription(getContext()
+ .getString(
+ R.string.app_installing_title, info.title, percentageString));
+ } else if ((info.runtimeStatusFlags
+ & ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) {
+ setContentDescription(getContext()
+ .getString(
+ R.string.app_downloading_title, info.title, percentageString));
+ }
+ }
+ }
+
/**
* Sets the icon for this view based on the layout direction.
*/
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0274775..2334267 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1917,6 +1917,13 @@
@Override
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+ if (isViewInTaskbar(v)) {
+ // Start the activity without the hacky workarounds below, which assume the View was
+ // clicked when Launcher was resumed and will be hidden until Launcher is re-resumed
+ // (this isn't the case for Taskbar).
+ return super.startActivitySafely(v, intent, item);
+ }
+
if (!hasBeenResumed()) {
// Workaround an issue where the WM launch animation is clobbered when finishing the
// recents animation into launcher. Defer launching the activity until Launcher is
@@ -2818,6 +2825,13 @@
.start();
}
+ /**
+ * @return Whether the View is in the same window as the Taskbar window.
+ */
+ public boolean isViewInTaskbar(View v) {
+ return false;
+ }
+
private static class NonConfigInstance {
public Configuration config;
public Bitmap snapshot;
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index ac3ad9f..0fa441a 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -50,7 +50,7 @@
return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
}
- public boolean supportsAdaptiveIconAnimation() {
+ public boolean supportsAdaptiveIconAnimation(View clickedView) {
return false;
}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 3122c68..2647d6f 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -302,7 +302,7 @@
intent.setPackage(null);
}
}
- if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation()) {
+ if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation(v)) {
// Preload the icon to reduce latency b/w swapping the floating view with the original.
FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
}