Snap for 9815702 from 94b5eada28 to udc-release
Change-Id: Ia54b31af64282f0892c17bd1444c7040dd5d4d5b
This commit is contained in:
@@ -16,13 +16,13 @@
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6.49,14.86c-0.66-0.39-0.66-1.34,0-1.73l6.02-3.53l5.89-3.46C19.11,5.73,20,6.26,20,7.1V14v6.9 c0,0.84-0.89,1.37-1.6,0.95l-5.89-3.46L6.49,14.86z" />
|
||||
android:pathData="M15.5417 1.66669C15.1833 1.66669 14.8417 1.76669 14.5333 1.94169L3.21667 8.74169C2.775 9.00002 2.5 9.48335 2.5 10C2.5 10.5167 2.775 11 3.21667 11.2584L14.5333 18.05C14.8417 18.2334 15.1833 18.325 15.5417 18.325C16.625 18.325 17.5 17.45 17.5 16.3667V3.62502C17.5 2.54169 16.625 1.66669 15.5417 1.66669Z" />
|
||||
</vector>
|
||||
@@ -16,12 +16,12 @@
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M 14 7 C 17.8659932488 7 21 10.1340067512 21 14 C 21 17.8659932488 17.8659932488 21 14 21 C 10.1340067512 21 7 17.8659932488 7 14 C 7 10.1340067512 10.1340067512 7 14 7 Z" />
|
||||
android:pathData="M10.0001 18.3334C5.40008 18.3334 1.66675 14.6 1.66675 10C1.66675 5.40002 5.40008 1.66669 10.0001 1.66669C14.6001 1.66669 18.3334 5.40002 18.3334 10C18.3334 14.6 14.6001 18.3334 10.0001 18.3334Z" />
|
||||
</vector>
|
||||
@@ -16,12 +16,12 @@
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28"
|
||||
android:viewportHeight="28">
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.9,21.5H8.1c-0.88,0-1.6-0.72-1.6-1.6V8.1c0-0.88,0.72-1.6,1.6-1.6h11.8c0.88,0,1.6,0.72,1.6,1.6v11.8 C21.5,20.78,20.78,21.5,19.9,21.5z" />
|
||||
android:pathData="M4.47634 2.5H15.5241C16.6164 2.5 17.5002 3.38382 17.5002 4.4761V15.5239C17.5002 16.6162 16.6164 17.5 15.5241 17.5H4.47634C3.38407 17.5 2.50024 16.6162 2.50024 15.5239V4.4761C2.50024 3.38382 3.38407 2.5 4.47634 2.5Z" />
|
||||
</vector>
|
||||
@@ -21,6 +21,8 @@
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:fitsSystemWindows="true"
|
||||
android:focusable="true"
|
||||
android:importantForAccessibility="yes"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<dimen name="transient_taskbar_icon_size">52dp</dimen>
|
||||
|
||||
<!-- Taskbar swipe up thresholds -->
|
||||
<dimen name="taskbar_nav_threshold">30dp</dimen>
|
||||
<dimen name="taskbar_from_nav_threshold">30dp</dimen>
|
||||
<dimen name="taskbar_app_window_threshold">100dp</dimen>
|
||||
<dimen name="taskbar_home_overview_threshold">180dp</dimen>
|
||||
<dimen name="taskbar_catch_up_threshold">300dp</dimen>
|
||||
|
||||
@@ -305,10 +305,12 @@
|
||||
<!-- An additional touch slop to prevent x-axis movement during the swipe up to show taskbar -->
|
||||
<dimen name="transient_taskbar_clamped_offset_bound">16dp</dimen>
|
||||
<!-- Taskbar swipe up thresholds -->
|
||||
<dimen name="taskbar_nav_threshold">40dp</dimen>
|
||||
<dimen name="taskbar_from_nav_threshold">40dp</dimen>
|
||||
<dimen name="taskbar_app_window_threshold">88dp</dimen>
|
||||
<dimen name="taskbar_home_overview_threshold">156dp</dimen>
|
||||
<dimen name="taskbar_catch_up_threshold">264dp</dimen>
|
||||
<!-- Taskbar swipe down threshold -->
|
||||
<dimen name="taskbar_to_nav_threshold">24dp</dimen>
|
||||
|
||||
<!-- Taskbar 3 button spacing -->
|
||||
<dimen name="taskbar_button_space_inbetween">24dp</dimen>
|
||||
|
||||
@@ -69,6 +69,10 @@ public class StashedHandleView extends View {
|
||||
*/
|
||||
public void updateSampledRegion(Rect stashedHandleBounds) {
|
||||
getLocationOnScreen(mTmpArr);
|
||||
// Translations are temporary due to animations, remove them for the purpose of determining
|
||||
// the final region we want sampled.
|
||||
mTmpArr[0] -= Math.round(getTranslationX());
|
||||
mTmpArr[1] -= Math.round(getTranslationY());
|
||||
mSampledRegion.set(stashedHandleBounds);
|
||||
mSampledRegion.offset(mTmpArr[0], mTmpArr[1]);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@ import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLA
|
||||
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW;
|
||||
import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
@@ -572,9 +571,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
|
||||
mControllers.navbarButtonsViewController.updateStateForSysuiFlags(systemUiStateFlags,
|
||||
fromInit);
|
||||
int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
|
||||
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
onNotificationShadeExpandChanged((systemUiStateFlags & shadeExpandedFlags) != 0, fromInit);
|
||||
boolean isShadeVisible = (systemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0;
|
||||
onNotificationShadeExpandChanged(isShadeVisible, fromInit);
|
||||
mControllers.taskbarViewController.setRecentsButtonDisabled(
|
||||
mControllers.navbarButtonsViewController.isRecentsDisabled()
|
||||
|| isNavBarKidsModeActive());
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.Utilities.mapRange
|
||||
import com.android.launcher3.Utilities.mapToRange
|
||||
import com.android.launcher3.anim.Interpolators
|
||||
@@ -31,6 +32,9 @@ import com.android.launcher3.util.DisplayController
|
||||
/** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
|
||||
class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
|
||||
private val DARK_THEME_SHADOW_ALPHA = 51f
|
||||
private val LIGHT_THEME_SHADOW_ALPHA = 25f
|
||||
|
||||
val paint = Paint()
|
||||
val lastDrawnTransientRect = RectF()
|
||||
var backgroundHeight = context.deviceProfile.taskbarSize.toFloat()
|
||||
@@ -42,6 +46,7 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
|
||||
private val isTransientTaskbar = DisplayController.isTransientTaskbar(context)
|
||||
|
||||
private val shadowAlpha: Float
|
||||
private var shadowBlur = 0f
|
||||
private var keyShadowDistance = 0f
|
||||
private var bottomMargin = 0
|
||||
@@ -73,6 +78,10 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
|
||||
}
|
||||
|
||||
shadowAlpha =
|
||||
if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
|
||||
else LIGHT_THEME_SHADOW_ALPHA
|
||||
|
||||
setCornerRoundness(DEFAULT_ROUNDNESS)
|
||||
}
|
||||
|
||||
@@ -142,13 +151,13 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
-mapRange(1f - progress, 0f, stashedHandleHeight / 2f)
|
||||
|
||||
// Draw shadow.
|
||||
val shadowAlpha =
|
||||
mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, 25f, Interpolators.LINEAR)
|
||||
val newShadowAlpha =
|
||||
mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
|
||||
paint.setShadowLayer(
|
||||
shadowBlur,
|
||||
0f,
|
||||
keyShadowDistance,
|
||||
setColorAlphaBound(Color.BLACK, Math.round(shadowAlpha))
|
||||
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
|
||||
)
|
||||
|
||||
lastDrawnTransientRect.set(
|
||||
|
||||
@@ -116,22 +116,6 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (mControllerCallbacks != null) {
|
||||
mControllerCallbacks.tryStashBasedOnMotionEvent(ev);
|
||||
}
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (mControllerCallbacks != null) {
|
||||
mControllerCallbacks.tryStashBasedOnMotionEvent(ev);
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRemoved(View child) {
|
||||
super.onViewRemoved(child);
|
||||
|
||||
@@ -18,15 +18,12 @@ package com.android.launcher3.taskbar;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.testing.shared.ResourceUtils;
|
||||
import com.android.launcher3.util.DimensionUtils;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -40,7 +37,6 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarDragLayer mTaskbarDragLayer;
|
||||
private final int mFolderMargin;
|
||||
private float mGestureHeightYThreshold;
|
||||
|
||||
// Alpha properties for taskbar background.
|
||||
private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
|
||||
@@ -59,6 +55,7 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
private TaskbarStashViaTouchController mTaskbarStashViaTouchController;
|
||||
private AnimatedFloat mOnBackgroundNavButtonColorIntensity;
|
||||
|
||||
private float mLastSetBackgroundAlpha;
|
||||
@@ -69,11 +66,11 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
mTaskbarDragLayer = taskbarDragLayer;
|
||||
final Resources resources = mTaskbarDragLayer.getResources();
|
||||
mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
|
||||
updateGestureHeight();
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
mTaskbarStashViaTouchController = new TaskbarStashViaTouchController(mControllers);
|
||||
mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
|
||||
|
||||
mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
|
||||
@@ -130,17 +127,11 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
return mBgOffset;
|
||||
}
|
||||
|
||||
private void updateGestureHeight() {
|
||||
int gestureHeight = ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
|
||||
mActivity.getResources());
|
||||
mGestureHeightYThreshold = mActivity.getDeviceProfile().heightPx - gestureHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make updates when configuration changes.
|
||||
*/
|
||||
public void onConfigurationChanged() {
|
||||
updateGestureHeight();
|
||||
mTaskbarStashViaTouchController.updateGestureHeight();
|
||||
}
|
||||
|
||||
private void updateBackgroundAlpha() {
|
||||
@@ -213,8 +204,6 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
*/
|
||||
public class TaskbarDragLayerCallbacks {
|
||||
|
||||
private final int[] mTempOutLocation = new int[2];
|
||||
|
||||
/**
|
||||
* Called to update the touchable insets.
|
||||
* @see ViewTreeObserver.InternalInsetsInfo#setTouchableInsets(int)
|
||||
@@ -223,37 +212,6 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
mControllers.taskbarInsetsController.updateInsetsTouchability(insetsInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to TaskbarDragLayer touch events and responds accordingly.
|
||||
*/
|
||||
public void tryStashBasedOnMotionEvent(MotionEvent ev) {
|
||||
if (!DisplayController.isTransientTaskbar(mActivity)) {
|
||||
return;
|
||||
}
|
||||
if (mControllers.taskbarStashController.isStashed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean stashTaskbar = false;
|
||||
|
||||
MotionEvent screenCoordinates = MotionEvent.obtain(ev);
|
||||
if (ev.getAction() == MotionEvent.ACTION_OUTSIDE) {
|
||||
stashTaskbar = true;
|
||||
} else if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mTaskbarDragLayer.getLocationOnScreen(mTempOutLocation);
|
||||
screenCoordinates.offsetLocation(mTempOutLocation[0], mTempOutLocation[1]);
|
||||
|
||||
if (!mControllers.taskbarViewController.isEventOverAnyItem(screenCoordinates)
|
||||
&& screenCoordinates.getY() < mGestureHeightYThreshold) {
|
||||
stashTaskbar = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (stashTaskbar) {
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child is removed from TaskbarDragLayer.
|
||||
*/
|
||||
@@ -283,9 +241,12 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
* Returns touch controllers.
|
||||
*/
|
||||
public TouchController[] getTouchControllers() {
|
||||
return new TouchController[]{mActivity.getDragController(),
|
||||
return new TouchController[] {
|
||||
mActivity.getDragController(),
|
||||
mControllers.taskbarForceVisibleImmersiveController,
|
||||
mControllers.navbarButtonsViewController.getTouchController()};
|
||||
mControllers.navbarButtonsViewController.getTouchController(),
|
||||
mTaskbarStashViaTouchController,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@ package com.android.launcher3.taskbar
|
||||
|
||||
import android.graphics.PorterDuff.Mode.SRC_ATOP
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.annotation.LayoutRes
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
@@ -153,6 +156,7 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
|
||||
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
|
||||
true
|
||||
)
|
||||
|
||||
tooltip.onCloseCallback = {
|
||||
this.tooltip = null
|
||||
controllers.taskbarAutohideSuspendController.updateFlag(
|
||||
@@ -161,11 +165,47 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
|
||||
)
|
||||
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
|
||||
}
|
||||
tooltip.accessibilityDelegate = createAccessibilityDelegate()
|
||||
|
||||
overlayContext.layoutInflater.inflate(contentResId, tooltip.content, true)
|
||||
this.tooltip = tooltip
|
||||
}
|
||||
|
||||
private fun createAccessibilityDelegate() =
|
||||
object : View.AccessibilityDelegate() {
|
||||
override fun performAccessibilityAction(
|
||||
host: View?,
|
||||
action: Int,
|
||||
args: Bundle?
|
||||
): Boolean {
|
||||
if (action == R.id.close) {
|
||||
hide()
|
||||
return true
|
||||
}
|
||||
return super.performAccessibilityAction(host, action, args)
|
||||
}
|
||||
|
||||
override fun onPopulateAccessibilityEvent(host: View?, event: AccessibilityEvent?) {
|
||||
super.onPopulateAccessibilityEvent(host, event)
|
||||
if (event?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
|
||||
event.text?.add(host?.context?.getText(R.string.taskbar_edu_a11y_title))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInitializeAccessibilityNodeInfo(
|
||||
host: View?,
|
||||
info: AccessibilityNodeInfo?
|
||||
) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
info?.addAction(
|
||||
AccessibilityNodeInfo.AccessibilityAction(
|
||||
R.id.close,
|
||||
host?.context?.getText(R.string.taskbar_edu_close)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dumpLogs(prefix: String?, pw: PrintWriter?) {
|
||||
pw?.println(prefix + "TaskbarEduTooltipController:")
|
||||
pw?.println("$prefix\tisTooltipEnabled=$isTooltipEnabled")
|
||||
|
||||
@@ -5,6 +5,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_ON;
|
||||
@@ -34,7 +35,8 @@ public class TaskbarKeyguardController implements TaskbarControllers.LoggableTas
|
||||
// locked.
|
||||
public static final int MASK_ANY_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
|
||||
| SYSUI_STATE_DEVICE_DREAMING;
|
||||
|
||||
private final TaskbarActivityContext mContext;
|
||||
private int mKeyguardSysuiFlags;
|
||||
|
||||
@@ -457,8 +457,13 @@ public class TaskbarLauncherStateController {
|
||||
animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness));
|
||||
}
|
||||
|
||||
if (hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED)) {
|
||||
// When transitioning between locked/unlocked, there is no stashing animation.
|
||||
// Keep isUnlockTransition in sync with its counterpart in
|
||||
// TaskbarStashController#createAnimToIsStashed.
|
||||
boolean isUnlockTransition =
|
||||
hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
|
||||
if (isUnlockTransition) {
|
||||
// When transitioning to unlocked, ensure the hotseat is fully visible from the
|
||||
// beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
|
||||
mIconAlignment.cancelAnimation();
|
||||
// updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual
|
||||
// change in value
|
||||
@@ -553,12 +558,16 @@ public class TaskbarLauncherStateController {
|
||||
animatorSet.play(stashAnimator);
|
||||
}
|
||||
|
||||
if (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL) {
|
||||
// Translate back to 0 at a shorter or same duration as the icon alignment animation.
|
||||
// This ensures there is no jump after switching to hotseat, e.g. when swiping up from
|
||||
// overview to home. Currently we do duration / 2 just to make it feel snappier.
|
||||
// Translate back to 0 at a shorter or same duration as the icon alignment animation.
|
||||
// This ensures there is no jump after switching to hotseat, e.g. when swiping up from
|
||||
// overview to home. When not in app, we do duration / 2 just to make it feel snappier.
|
||||
long resetDuration = mControllers.taskbarStashController.isInApp()
|
||||
? duration
|
||||
: duration / 2;
|
||||
if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration)
|
||||
&& (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) {
|
||||
animatorSet.play(mControllers.taskbarTranslationController
|
||||
.createAnimToResetTranslation(duration / 2));
|
||||
.createAnimToResetTranslation(resetDuration));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -201,14 +201,12 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
// Create a Stream of all applicable system shortcuts
|
||||
private Stream<SystemShortcut.Factory> getSystemShortcuts() {
|
||||
// concat a Stream of split options with a Stream of APP_INFO
|
||||
Stream<SystemShortcut.Factory> appInfo = Stream.of(APP_INFO);
|
||||
|
||||
// append split options to APP_INFO shortcut, the order here will reflect in the popup
|
||||
return Stream.concat(
|
||||
Stream.of(APP_INFO),
|
||||
Utilities.getSplitPositionOptions(mContext.getDeviceProfile())
|
||||
.stream()
|
||||
.map(this::createSplitShortcutFactory),
|
||||
appInfo
|
||||
.map(this::createSplitShortcutFactory)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,7 @@ import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
|
||||
|
||||
import android.animation.Animator;
|
||||
@@ -586,9 +585,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
// already stashed Taskbar.
|
||||
boolean hotseatTopElement = mControllers.uiController.isHotseatIconOnTopWhenAligned()
|
||||
|| !hasAnyFlag(changedFlags, FLAG_IN_APP);
|
||||
// If transitioning between locked/unlocked device, do not play a stash animation.
|
||||
boolean unLockedTransition = hasAnyFlag(changedFlags, FLAG_STASHED_DEVICE_LOCKED);
|
||||
boolean skipStashAnimation = !hotseatTopElement || unLockedTransition;
|
||||
// If transitioning to unlocked device, do not play a stash animation.
|
||||
// Keep isUnlockTransition in sync with its counterpart in
|
||||
// TaskbarLauncherStateController#onStateChangeApplied.
|
||||
boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_STASHED_DEVICE_LOCKED)
|
||||
&& !hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED);
|
||||
boolean skipStashAnimation = !hotseatTopElement || isUnlockTransition;
|
||||
|
||||
if (isTransientTaskbar) {
|
||||
createTransientAnimToIsStashed(mAnimator, isStashed, duration, animateBg, changedFlags,
|
||||
@@ -907,12 +909,20 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
long startDelay = 0;
|
||||
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_SYSUI, hasAnyFlag(systemUiStateFlags,
|
||||
SYSUI_STATE_QUICK_SETTINGS_EXPANDED
|
||||
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED));
|
||||
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE));
|
||||
updateStateForFlag(FLAG_STASHED_SYSUI,
|
||||
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
|
||||
updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
|
||||
hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED));
|
||||
|
||||
boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
|
||||
boolean wasLocked = hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED);
|
||||
updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
|
||||
|
||||
if (isLocked && !wasLocked && DisplayController.isTransientTaskbar(mActivity)) {
|
||||
// Stash the transient taskbar when locking the device. This improves the transition
|
||||
// to AoD (otherwise the taskbar stays a bit too long above the collapsing AoD scrim),
|
||||
// and ensures the taskar state is reset when unlocking the device afterwards.
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, true);
|
||||
}
|
||||
|
||||
// Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
|
||||
mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.MotionEvent
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.anim.Interpolators.LINEAR
|
||||
import com.android.launcher3.testing.shared.ResourceUtils
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.TouchController
|
||||
import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer
|
||||
|
||||
/**
|
||||
* A helper [TouchController] for [TaskbarDragLayerController], specifically to handle touch events
|
||||
* to stash Transient Taskbar. There are two cases to handle:
|
||||
* - A touch outside of Transient Taskbar bounds will immediately stash on [MotionEvent.ACTION_DOWN]
|
||||
* or [MotionEvent.ACTION_OUTSIDE].
|
||||
* - Touches inside Transient Taskbar bounds will stash if it is detected as a swipe down gesture.
|
||||
*
|
||||
* Note: touches to *unstash* Taskbar are handled by [TaskbarStashInputConsumer].
|
||||
*/
|
||||
class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : TouchController {
|
||||
|
||||
private val activity: TaskbarActivityContext = controllers.taskbarActivityContext
|
||||
private val enabled = DisplayController.isTransientTaskbar(activity)
|
||||
private val swipeDownDetector: SingleAxisSwipeDetector
|
||||
private val translationCallback = controllers.taskbarTranslationController.transitionCallback
|
||||
/** Interpolator to apply resistance as user swipes down to the bottom of the screen. */
|
||||
private val displacementInterpolator = LINEAR
|
||||
/** How far we can translate the TaskbarView before it's offscreen. */
|
||||
private val maxVisualDisplacement =
|
||||
activity.resources.getDimensionPixelSize(R.dimen.transient_taskbar_margin).toFloat()
|
||||
/** How far the swipe could go, if user swiped from the very top of TaskbarView. */
|
||||
private val maxTouchDisplacement = maxVisualDisplacement + activity.deviceProfile.taskbarSize
|
||||
private val touchDisplacementToStash =
|
||||
activity.resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold).toFloat()
|
||||
|
||||
/** The height of the system gesture region, so we don't stash when touching down there. */
|
||||
private var gestureHeightYThreshold = 0f
|
||||
|
||||
init {
|
||||
updateGestureHeight()
|
||||
swipeDownDetector = SingleAxisSwipeDetector(activity, createSwipeListener(), VERTICAL)
|
||||
swipeDownDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false)
|
||||
}
|
||||
|
||||
fun updateGestureHeight() {
|
||||
if (!enabled) return
|
||||
|
||||
val gestureHeight: Int =
|
||||
ResourceUtils.getNavbarSize(
|
||||
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
|
||||
activity.resources
|
||||
)
|
||||
gestureHeightYThreshold = (activity.deviceProfile.heightPx - gestureHeight).toFloat()
|
||||
}
|
||||
|
||||
private fun createSwipeListener() =
|
||||
object : SingleAxisSwipeDetector.Listener {
|
||||
private var lastDisplacement = 0f
|
||||
|
||||
override fun onDragStart(start: Boolean, startDisplacement: Float) {}
|
||||
|
||||
override fun onDrag(displacement: Float): Boolean {
|
||||
lastDisplacement = displacement
|
||||
if (displacement < 0) return false
|
||||
// Apply resistance so that the visual displacement doesn't go beyond the screen.
|
||||
translationCallback.onActionMove(
|
||||
Utilities.mapToRange(
|
||||
displacement,
|
||||
0f,
|
||||
maxTouchDisplacement,
|
||||
0f,
|
||||
maxVisualDisplacement,
|
||||
displacementInterpolator
|
||||
)
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDragEnd(velocity: Float) {
|
||||
val isFlingDown = swipeDownDetector.isFling(velocity) && velocity > 0
|
||||
val isSignificantDistance = lastDisplacement > touchDisplacementToStash
|
||||
if (isFlingDown || isSignificantDistance) {
|
||||
// Successfully triggered stash.
|
||||
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
|
||||
}
|
||||
translationCallback.onActionEnd()
|
||||
swipeDownDetector.finishedScrolling()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (!enabled || controllers.taskbarStashController.isStashed) {
|
||||
return false
|
||||
}
|
||||
|
||||
val screenCoordinatesEv = MotionEvent.obtain(ev)
|
||||
screenCoordinatesEv.setLocation(ev.rawX, ev.rawY)
|
||||
if (ev.action == MotionEvent.ACTION_OUTSIDE) {
|
||||
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
|
||||
} else if (controllers.taskbarViewController.isEventOverAnyItem(screenCoordinatesEv)) {
|
||||
swipeDownDetector.onTouchEvent(ev)
|
||||
if (swipeDownDetector.isDraggingState) {
|
||||
return true
|
||||
}
|
||||
} else if (ev.action == MotionEvent.ACTION_DOWN) {
|
||||
if (screenCoordinatesEv.y < gestureHeightYThreshold) {
|
||||
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onControllerTouchEvent(ev: MotionEvent) = swipeDownDetector.onTouchEvent(ev)
|
||||
}
|
||||
@@ -133,6 +133,21 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
|
||||
return mCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we will animate to zero before the input duration.
|
||||
*/
|
||||
public boolean willAnimateToZeroBefore(long duration) {
|
||||
if (mSpringBounce != null && mSpringBounce.isRunning()) {
|
||||
long springDuration = mSpringBounce.getDuration();
|
||||
long current = mSpringBounce.getCurrentPlayTime();
|
||||
return (springDuration - current < duration);
|
||||
}
|
||||
if (mTranslationYForSwipe.isAnimatingToValue(0)) {
|
||||
return mTranslationYForSwipe.getRemainingTime() < duration;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an animation to reset the taskbar translation to {@code 0}.
|
||||
*/
|
||||
|
||||
@@ -424,20 +424,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
mControllerCallbacks.onInterceptTouchEvent(ev);
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mIconLayoutBounds.left <= event.getX()
|
||||
&& event.getX() <= mIconLayoutBounds.right
|
||||
&& !DisplayController.isTransientTaskbar(mActivityContext)) {
|
||||
// Don't allow long pressing between icons, or above/below them
|
||||
// unless its transient taskbar.
|
||||
mControllerCallbacks.clearTouchInProgress();
|
||||
if (mIconLayoutBounds.left <= event.getX() && event.getX() <= mIconLayoutBounds.right) {
|
||||
// Don't allow long pressing between icons, or above/below them.
|
||||
return true;
|
||||
}
|
||||
if (mControllerCallbacks.onTouchEvent(event)) {
|
||||
|
||||
@@ -26,8 +26,6 @@ import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
|
||||
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
|
||||
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
|
||||
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
||||
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
|
||||
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
|
||||
@@ -61,7 +59,6 @@ import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.icons.ThemedIconDrawable;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.LauncherBindableItemsContainer;
|
||||
@@ -116,9 +113,6 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
|
||||
private final TaskbarModelCallbacks mModelCallbacks;
|
||||
|
||||
// Captures swipe down action to close transient Taskbar.
|
||||
protected @Nullable SingleAxisSwipeDetector mSwipeDownDetector;
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
@@ -155,31 +149,6 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
colorHSL[2] = TASKBAR_DARK_THEME_ICONS_BACKGROUND_LUMINANCE;
|
||||
mTaskbarThemedIconsBackgroundColor = ColorUtils.HSLToColor(colorHSL);
|
||||
}
|
||||
|
||||
if (DisplayController.isTransientTaskbar(mActivity)) {
|
||||
mSwipeDownDetector = new SingleAxisSwipeDetector(activity,
|
||||
new SingleAxisSwipeDetector.Listener() {
|
||||
private float mLastDisplacement;
|
||||
|
||||
@Override
|
||||
public boolean onDrag(float displacement) {
|
||||
mLastDisplacement = displacement;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragEnd(float velocity) {
|
||||
if (mLastDisplacement > 0) {
|
||||
mControllers.taskbarStashController
|
||||
.updateAndAnimateTransientTaskbar(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragStart(boolean start, float startDisplacement) {}
|
||||
}, VERTICAL);
|
||||
mSwipeDownDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
|
||||
}
|
||||
mIsRtl = Utilities.isRtl(mTaskbarView.getResources());
|
||||
}
|
||||
|
||||
@@ -659,8 +628,6 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
private float mDownX, mDownY;
|
||||
private boolean mCanceledStashHint;
|
||||
|
||||
private boolean mTouchInProgress;
|
||||
|
||||
public View.OnClickListener getIconOnClickListener() {
|
||||
return mActivity.getItemOnClickListener();
|
||||
}
|
||||
@@ -681,76 +648,40 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
.updateAndAnimateIsManuallyStashedInApp(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply listens to all intercept touch events passed to TaskbarView.
|
||||
*/
|
||||
public void onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mTouchInProgress = true;
|
||||
}
|
||||
|
||||
if (mTouchInProgress && mSwipeDownDetector != null) {
|
||||
mSwipeDownDetector.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
if (ev.getAction() == MotionEvent.ACTION_UP
|
||||
|| ev.getAction() == MotionEvent.ACTION_CANCEL) {
|
||||
clearTouchInProgress();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first chance to handle TaskbarView#onTouchEvent, and return whether we want to
|
||||
* consume the touch so TaskbarView treats it as an ACTION_CANCEL.
|
||||
* TODO(b/270395798): We can remove this entirely once we remove the Transient Taskbar flag.
|
||||
*/
|
||||
public boolean onTouchEvent(MotionEvent motionEvent) {
|
||||
boolean shouldConsumeTouch = false;
|
||||
boolean clearTouchInProgress = false;
|
||||
|
||||
final float x = motionEvent.getRawX();
|
||||
final float y = motionEvent.getRawY();
|
||||
switch (motionEvent.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mTouchInProgress = true;
|
||||
mDownX = x;
|
||||
mDownY = y;
|
||||
mControllers.taskbarStashController.startStashHint(/* animateForward = */ true);
|
||||
mCanceledStashHint = false;
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (mTouchInProgress
|
||||
&& !mCanceledStashHint
|
||||
if (!mCanceledStashHint
|
||||
&& squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
|
||||
mControllers.taskbarStashController.startStashHint(
|
||||
/* animateForward= */ false);
|
||||
mCanceledStashHint = true;
|
||||
shouldConsumeTouch = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (mTouchInProgress && !mCanceledStashHint) {
|
||||
if (!mCanceledStashHint) {
|
||||
mControllers.taskbarStashController.startStashHint(
|
||||
/* animateForward= */ false);
|
||||
}
|
||||
clearTouchInProgress = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mTouchInProgress && mSwipeDownDetector != null) {
|
||||
mSwipeDownDetector.onTouchEvent(motionEvent);
|
||||
}
|
||||
if (clearTouchInProgress) {
|
||||
clearTouchInProgress();
|
||||
}
|
||||
return shouldConsumeTouch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that we do not pass any more touch events to the SwipeDetector.
|
||||
*/
|
||||
public void clearTouchInProgress() {
|
||||
mTouchInProgress = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
@@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
|
||||
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
|
||||
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
|
||||
@@ -161,6 +162,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (isTrackpadMultiFingerSwipe(ev)) {
|
||||
return false;
|
||||
}
|
||||
int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
|
||||
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
|
||||
return false;
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.quickstep;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.Surface.ROTATION_0;
|
||||
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
|
||||
@@ -243,8 +242,8 @@ public class RotationTouchHelper implements DisplayInfoChangeListener {
|
||||
*/
|
||||
public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex,
|
||||
BaseActivityInterface activity) {
|
||||
if (isTrackpadMotionEvent(event)) {
|
||||
return isTrackpadMultiFingerSwipe(event) && !activity.isResumed();
|
||||
if (isTrackpadMultiFingerSwipe(event)) {
|
||||
return true;
|
||||
}
|
||||
return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
|
||||
event.getY(pointerIndex));
|
||||
|
||||
@@ -75,7 +75,7 @@ public class TaskbarStashInputConsumer extends DelegateInputConsumer {
|
||||
|
||||
Resources res = context.getResources();
|
||||
mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
|
||||
mTaskbarNavThreshold = res.getDimensionPixelSize(R.dimen.taskbar_nav_threshold);
|
||||
mTaskbarNavThreshold = res.getDimensionPixelSize(R.dimen.taskbar_from_nav_threshold);
|
||||
mTaskbarNavThresholdY = taskbarActivityContext.getDeviceProfile().heightPx
|
||||
- mTaskbarNavThreshold;
|
||||
mIsTaskbarAllAppsOpen =
|
||||
|
||||
@@ -225,10 +225,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
private static final String TAG = "RecentsView";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
// TODO(b/184899234): We use this timeout to wait a fixed period after switching to the
|
||||
// screenshot when dismissing the current live task to ensure the app can try and get stopped.
|
||||
private static final int REMOVE_TASK_WAIT_FOR_APP_STOP_MS = 100;
|
||||
|
||||
public static final FloatProperty<RecentsView> CONTENT_ALPHA =
|
||||
new FloatProperty<RecentsView>("contentAlpha") {
|
||||
@Override
|
||||
@@ -3893,14 +3889,13 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
int[] taskIds = getTaskIdsForTaskViewId(dismissedTaskViewId);
|
||||
int primaryTaskId = taskIds[0];
|
||||
int secondaryTaskId = taskIds[1];
|
||||
UI_HELPER_EXECUTOR.getHandler().postDelayed(
|
||||
UI_HELPER_EXECUTOR.getHandler().post(
|
||||
() -> {
|
||||
ActivityManagerWrapper.getInstance().removeTask(primaryTaskId);
|
||||
if (secondaryTaskId != -1) {
|
||||
ActivityManagerWrapper.getInstance().removeTask(secondaryTaskId);
|
||||
}
|
||||
},
|
||||
REMOVE_TASK_WAIT_FOR_APP_STOP_MS);
|
||||
});
|
||||
}
|
||||
|
||||
protected void onDismissAnimationEnds() {
|
||||
@@ -3923,9 +3918,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
|
||||
if (isSuccess) {
|
||||
// Remove all the task views now
|
||||
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
|
||||
UI_HELPER_EXECUTOR.getHandler().postDelayed(
|
||||
ActivityManagerWrapper.getInstance()::removeAllRecentTasks,
|
||||
REMOVE_TASK_WAIT_FOR_APP_STOP_MS);
|
||||
UI_HELPER_EXECUTOR.getHandler().post(
|
||||
ActivityManagerWrapper.getInstance()::removeAllRecentTasks);
|
||||
removeTasksViewsAndClearAllButton();
|
||||
startHome();
|
||||
});
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@android:color/system_neutral1_500" android:lStar="15" />
|
||||
<item android:color="@android:color/system_neutral1_500" android:lStar="20" />
|
||||
</selector>
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@android:color/system_neutral1_500" android:lStar="95" />
|
||||
<item android:color="@android:color/system_neutral1_500" android:lStar="98" />
|
||||
</selector>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<com.android.launcher3.widget.picker.WidgetsFullSheet xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<com.android.launcher3.widget.picker.WidgetsTwoPaneSheet
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -125,4 +125,4 @@
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</com.android.launcher3.views.SpringRelativeLayout>
|
||||
</com.android.launcher3.widget.picker.WidgetsFullSheet>
|
||||
</com.android.launcher3.widget.picker.WidgetsTwoPaneSheet>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<item type="id" name="home" />
|
||||
<item type="id" name="recent_apps" />
|
||||
<item type="id" name="back" />
|
||||
<item type="id" name="close"/>
|
||||
<item type="id" name="ime_switcher" />
|
||||
<item type="id" name="accessibility_button" />
|
||||
<item type="id" name="rotate_suggestion" />
|
||||
@@ -41,4 +42,5 @@
|
||||
|
||||
<item type="id" name="saved_floating_widget_foreground" />
|
||||
<item type="id" name="saved_floating_widget_background" />
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -285,6 +285,10 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
||||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
|
||||
Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
|
||||
UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
|
||||
if (TestProtocol.sDebugTracing) {
|
||||
Log.d(TestProtocol.WORK_TAB_MISSING, "onBroadcastIntent intentAction: " + action +
|
||||
" user: " + user);
|
||||
}
|
||||
if (user != null) {
|
||||
if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
|
||||
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
|
||||
|
||||
@@ -132,6 +132,15 @@ public class AnimatedFloat {
|
||||
return isAnimating() && mEndValue != null && mEndValue == endValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remaining time of the existing animation (if any).
|
||||
*/
|
||||
public long getRemainingTime() {
|
||||
return isAnimating() && mValueAnimator.isRunning()
|
||||
? Math.max(0, mValueAnimator.getDuration() - mValueAnimator.getCurrentPlayTime())
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we are currently not animating, and the animation's value matches the given.
|
||||
*/
|
||||
|
||||
@@ -66,6 +66,9 @@ public class GridCustomizationsProvider extends ContentProvider {
|
||||
|
||||
private static final String KEY_SURFACE_PACKAGE = "surface_package";
|
||||
private static final String KEY_CALLBACK = "callback";
|
||||
public static final String KEY_HIDE_BOTTOM_ROW = "hide_bottom_row";
|
||||
|
||||
private static final int MESSAGE_ID_UPDATE_PREVIEW = 1337;
|
||||
|
||||
private final ArrayMap<IBinder, PreviewLifecycleObserver> mActivePreviews = new ArrayMap<>();
|
||||
|
||||
@@ -79,7 +82,7 @@ public class GridCustomizationsProvider extends ContentProvider {
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
switch (uri.getPath()) {
|
||||
case KEY_LIST_OPTIONS: {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[] {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{
|
||||
KEY_NAME, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT, KEY_IS_DEFAULT});
|
||||
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
|
||||
for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
|
||||
@@ -95,7 +98,7 @@ public class GridCustomizationsProvider extends ContentProvider {
|
||||
}
|
||||
case GET_ICON_THEMED:
|
||||
case ICON_THEMED: {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[] {BOOLEAN_VALUE});
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{BOOLEAN_VALUE});
|
||||
cursor.newRow().add(BOOLEAN_VALUE, isThemedIconEnabled(getContext()) ? 1 : 0);
|
||||
return cursor;
|
||||
}
|
||||
@@ -224,7 +227,14 @@ public class GridCustomizationsProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message) {
|
||||
destroyObserver(this);
|
||||
if (destroyed) {
|
||||
return true;
|
||||
}
|
||||
if (message.what == MESSAGE_ID_UPDATE_PREVIEW) {
|
||||
renderer.hideBottomRow(message.getData().getBoolean(KEY_HIDE_BOTTOM_ROW));
|
||||
} else {
|
||||
destroyObserver(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -344,6 +344,25 @@ public class LauncherPreviewRenderer extends ContextWrapper
|
||||
return mHotseat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the components in the bottom row.
|
||||
*
|
||||
* @param hide True to hide and false to show.
|
||||
*/
|
||||
public void hideBottomRow(boolean hide) {
|
||||
mUiHandler.post(() -> {
|
||||
if (mDp.isTaskbarPresent) {
|
||||
// hotseat icons on bottom
|
||||
mHotseat.setIconsAlpha(hide ? 0 : 1);
|
||||
if (mDp.isQsbInline) {
|
||||
mHotseat.setQsbAlpha(hide ? 0 : 1);
|
||||
}
|
||||
} else {
|
||||
mHotseat.setQsbAlpha(hide ? 0 : 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellLayout getScreenWithId(int screenId) {
|
||||
return mWorkspaceScreens.get(screenId);
|
||||
|
||||
@@ -87,6 +87,8 @@ public class PreviewSurfaceRenderer {
|
||||
private final SurfaceControlViewHost mSurfaceControlViewHost;
|
||||
|
||||
private boolean mDestroyed = false;
|
||||
private LauncherPreviewRenderer mRenderer;
|
||||
private boolean mHideQsb;
|
||||
|
||||
public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
|
||||
mContext = context;
|
||||
@@ -97,6 +99,7 @@ public class PreviewSurfaceRenderer {
|
||||
gridName = InvariantDeviceProfile.getCurrentGridName(context);
|
||||
}
|
||||
mWallpaperColors = bundle.getParcelable(KEY_COLORS);
|
||||
mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
|
||||
mIdp = new InvariantDeviceProfile(context, gridName);
|
||||
|
||||
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
|
||||
@@ -174,6 +177,17 @@ public class PreviewSurfaceRenderer {
|
||||
MODEL_EXECUTOR.execute(this::loadModelData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the components in the bottom row.
|
||||
*
|
||||
* @param hide True to hide and false to show.
|
||||
*/
|
||||
public void hideBottomRow(boolean hide) {
|
||||
if (mRenderer != null) {
|
||||
mRenderer.hideBottomRow(hide);
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void loadModelData() {
|
||||
final boolean migrated = doGridMigrationIfNecessary();
|
||||
@@ -209,8 +223,8 @@ public class PreviewSurfaceRenderer {
|
||||
DeviceProfile deviceProfile = mIdp.getDeviceProfile(previewContext);
|
||||
String query =
|
||||
LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
|
||||
+ " or " + LauncherSettings.Favorites.CONTAINER + " = "
|
||||
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
+ " or " + LauncherSettings.Favorites.CONTAINER + " = "
|
||||
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
if (deviceProfile.isTwoPanels) {
|
||||
query += " or " + LauncherSettings.Favorites.SCREEN + " = "
|
||||
+ Workspace.SECOND_SCREEN_ID;
|
||||
@@ -254,8 +268,10 @@ public class PreviewSurfaceRenderer {
|
||||
if (mDestroyed) {
|
||||
return;
|
||||
}
|
||||
View view = new LauncherPreviewRenderer(inflationContext, mIdp, mWallpaperColors,
|
||||
launcherWidgetSpanInfo).getRenderedView(dataModel, widgetProviderInfoMap);
|
||||
mRenderer = new LauncherPreviewRenderer(inflationContext, mIdp,
|
||||
mWallpaperColors, launcherWidgetSpanInfo);
|
||||
mRenderer.hideBottomRow(mHideQsb);
|
||||
View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
|
||||
// This aspect scales the view to fit in the surface and centers it
|
||||
final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
|
||||
mHeight / (float) view.getMeasuredHeight());
|
||||
|
||||
@@ -140,13 +140,11 @@ public abstract class ArrowPopup<T extends Context & ActivityContext>
|
||||
mOutlineRadius = Themes.getDialogCornerRadius(context);
|
||||
mActivityContext = ActivityContext.lookupContext(context);
|
||||
mIsRtl = Utilities.isRtl(getResources());
|
||||
|
||||
int popupPrimaryColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
|
||||
mArrowColor = popupPrimaryColor;
|
||||
mElevation = getResources().getDimension(R.dimen.deep_shortcuts_elevation);
|
||||
|
||||
// Initialize arrow view
|
||||
final Resources resources = getResources();
|
||||
mArrowColor = getColorStateList(getContext(), R.color.popup_shade_first).getDefaultColor();
|
||||
mMargin = resources.getDimensionPixelSize(R.dimen.popup_margin);
|
||||
mArrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
|
||||
mArrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
|
||||
@@ -159,6 +157,7 @@ public abstract class ArrowPopup<T extends Context & ActivityContext>
|
||||
|
||||
int smallerRadius = resources.getDimensionPixelSize(R.dimen.popup_smaller_radius);
|
||||
mRoundedTop = new GradientDrawable();
|
||||
int popupPrimaryColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
|
||||
mRoundedTop.setColor(popupPrimaryColor);
|
||||
mRoundedTop.setCornerRadii(new float[] { mOutlineRadius, mOutlineRadius, mOutlineRadius,
|
||||
mOutlineRadius, smallerRadius, smallerRadius, smallerRadius, smallerRadius});
|
||||
|
||||
@@ -197,13 +197,13 @@ public class SecondaryDragLayer extends BaseDragLayer<SecondaryDisplayLauncher>
|
||||
return false;
|
||||
}
|
||||
|
||||
// order of this list will reflect in the popup
|
||||
List<SystemShortcut> systemShortcuts = new ArrayList<>();
|
||||
|
||||
systemShortcuts.add(APP_INFO.getShortcut(mActivity, item, v));
|
||||
// Hide redundant pin shortcut for app drawer icons if drag-n-drop is enabled.
|
||||
if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mActivity.isAppDrawerShown()) {
|
||||
systemShortcuts.add(mPinnedAppsAdapter.getSystemShortcut(item, v));
|
||||
}
|
||||
systemShortcuts.add(APP_INFO.getShortcut(mActivity, item, v));
|
||||
int deepShortcutCount = popupDataProvider.getShortcutCountForItem(item);
|
||||
final PopupContainerWithArrow<SecondaryDisplayLauncher> container;
|
||||
if (ENABLE_MATERIAL_U_POPUP.get()) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.launcher3.views;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_CANCEL;
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_OUTSIDE;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
|
||||
@@ -260,7 +261,10 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext>
|
||||
mTouchCompleteListener = null;
|
||||
}
|
||||
|
||||
if (mActiveController != null) {
|
||||
if (mActiveController != null && ev.getAction() != ACTION_OUTSIDE) {
|
||||
// For some reason, once we intercept touches and have an mActiveController, we won't
|
||||
// get onInterceptTouchEvent() for ACTION_OUTSIDE. Thus, we must recalculate a new
|
||||
// TouchController (if any) to handle the ACTION_OUTSIDE here in onTouchEvent() as well.
|
||||
return mActiveController.onControllerTouchEvent(ev);
|
||||
} else {
|
||||
// In case no child view handled the touch event, we may not get onIntercept anymore
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.view.WindowInsets;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Px;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -182,7 +181,6 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher>
|
||||
* Measures the dimension of this view and its children by taking system insets, navigation bar,
|
||||
* status bar, into account.
|
||||
*/
|
||||
@GuardedBy("MainThread")
|
||||
protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
|
||||
int widthUsed;
|
||||
|
||||
@@ -30,7 +30,6 @@ import android.content.Context;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
@@ -43,13 +42,10 @@ import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.window.BackEvent;
|
||||
|
||||
@@ -68,10 +64,7 @@ import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.model.UserManagerState;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.recyclerview.ViewHolderBinder;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.views.ArrowTipView;
|
||||
import com.android.launcher3.views.RecyclerViewFastScroller;
|
||||
import com.android.launcher3.views.SpringRelativeLayout;
|
||||
@@ -80,8 +73,6 @@ import com.android.launcher3.views.WidgetsEduView;
|
||||
import com.android.launcher3.widget.BaseWidgetSheet;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListContentEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
|
||||
import com.android.launcher3.widget.picker.search.SearchModeListener;
|
||||
import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
|
||||
import com.android.launcher3.widget.util.WidgetsTableUtils;
|
||||
@@ -89,7 +80,6 @@ import com.android.launcher3.workprofile.PersonalWorkPagedView;
|
||||
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -105,27 +95,22 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
private static final long EDUCATION_TIP_DELAY_MS = 200;
|
||||
private static final long EDUCATION_DIALOG_DELAY_MS = 500;
|
||||
private static final float VERTICAL_START_POSITION = 0.3f;
|
||||
private static final int PERSONAL_TAB = 0;
|
||||
private static final int WORK_TAB = 1;
|
||||
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
|
||||
// The widget recommendation table can easily take over the entire screen on devices with small
|
||||
// resolution or landscape on phone. This ratio defines the max percentage of content area that
|
||||
// the table can display.
|
||||
private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
|
||||
|
||||
private static final String KEY_WIDGETS_EDUCATION_DIALOG_SEEN =
|
||||
"launcher.widgets_education_dialog_seen";
|
||||
|
||||
private final UserManagerState mUserManagerState = new UserManagerState();
|
||||
|
||||
private final boolean mHasWorkProfile;
|
||||
private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
|
||||
private final UserHandle mCurrentUser = Process.myUserHandle();
|
||||
private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter =
|
||||
entry -> mCurrentUser.equals(entry.mPkgItem.user);
|
||||
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
|
||||
entry -> !mCurrentUser.equals(entry.mPkgItem.user)
|
||||
&& !mUserManagerState.isUserQuiet(entry.mPkgItem.user);
|
||||
protected final boolean mHasWorkProfile;
|
||||
protected final SparseArray<AdapterHolder> mAdapters = new SparseArray();
|
||||
@Nullable private ArrowTipView mLatestEducationalTip;
|
||||
private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
|
||||
new OnLayoutChangeListener() {
|
||||
@@ -175,52 +160,27 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
@Px private final int mTabsHeight;
|
||||
|
||||
@Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
|
||||
@Nullable private PersonalWorkPagedView mViewPager;
|
||||
@Nullable private WidgetsRecyclerView mCurrentTouchEventRecyclerView;
|
||||
@Nullable PersonalWorkPagedView mViewPager;
|
||||
private boolean mIsInSearchMode;
|
||||
private boolean mIsNoWidgetsViewNeeded;
|
||||
@Px private int mMaxSpanPerRow;
|
||||
private TextView mNoWidgetsView;
|
||||
|
||||
private StickyHeaderLayout mSearchScrollView;
|
||||
private WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
|
||||
private LinearLayout mSuggestedWidgetsContainer;
|
||||
private WidgetsListHeader mSuggestedWidgetsHeader;
|
||||
private View mTabBar;
|
||||
private View mSearchBarContainer;
|
||||
private WidgetsSearchBar mSearchBar;
|
||||
private TextView mHeaderTitle;
|
||||
private LinearLayout mRightPane;
|
||||
private FrameLayout mRightPaneScrollView;
|
||||
private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
|
||||
private DeviceProfile mDeviceProfile;
|
||||
private final boolean mIsTwoPane;
|
||||
|
||||
private int mOrientation;
|
||||
private @Nullable WidgetsRecyclerView mCurrentTouchEventRecyclerView;
|
||||
|
||||
private RecyclerViewFastScroller mFastScroller;
|
||||
|
||||
private final ViewOutlineProvider mViewOutlineProviderRightPane = new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRoundRect(
|
||||
0,
|
||||
0,
|
||||
view.getMeasuredWidth(),
|
||||
view.getMeasuredHeight() - getResources().getDimensionPixelSize(
|
||||
R.dimen.widget_list_horizontal_margin_large_screen),
|
||||
view.getResources().getDimensionPixelSize(
|
||||
R.dimen.widget_list_top_bottom_corner_radius)
|
||||
);
|
||||
}
|
||||
};
|
||||
protected TextView mNoWidgetsView;
|
||||
protected StickyHeaderLayout mSearchScrollView;
|
||||
protected WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
|
||||
protected View mTabBar;
|
||||
protected View mSearchBarContainer;
|
||||
protected WidgetsSearchBar mSearchBar;
|
||||
protected TextView mHeaderTitle;
|
||||
protected RecyclerViewFastScroller mFastScroller;
|
||||
|
||||
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
|
||||
mIsTwoPane = mDeviceProfile.isTablet
|
||||
&& mDeviceProfile.isLandscape
|
||||
&& LARGE_SCREEN_WIDGET_PICKER.get();
|
||||
mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
|
||||
mOrientation = Launcher.getLauncher(context).getOrientation();
|
||||
mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
|
||||
@@ -243,37 +203,39 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
mContent = findViewById(R.id.container);
|
||||
setContentBackgroundWithParent(getContext().getDrawable(R.drawable.bg_widgets_full_sheet),
|
||||
mContent);
|
||||
|
||||
mContent.setOutlineProvider(mViewOutlineProvider);
|
||||
mContent.setClipToOutline(true);
|
||||
setupSheet();
|
||||
}
|
||||
|
||||
protected void setupSheet() {
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
|
||||
int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
|
||||
: R.layout.widgets_full_sheet_recyclerview;
|
||||
layoutInflater.inflate(contentLayoutRes, mContent, true);
|
||||
|
||||
if (mIsTwoPane) {
|
||||
layoutInflater.inflate(
|
||||
mHasWorkProfile
|
||||
? R.layout.widgets_full_sheet_paged_view_large_screen
|
||||
: R.layout.widgets_full_sheet_recyclerview_large_screen,
|
||||
findViewById(R.id.recycler_view_container),
|
||||
true);
|
||||
} else {
|
||||
layoutInflater.inflate(
|
||||
mHasWorkProfile
|
||||
? R.layout.widgets_full_sheet_paged_view
|
||||
: R.layout.widgets_full_sheet_recyclerview,
|
||||
mContent,
|
||||
true);
|
||||
}
|
||||
setupViews();
|
||||
|
||||
mRecommendedWidgetsTable = mSearchScrollView.findViewById(R.id.recommended_widget_table);
|
||||
mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
|
||||
mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
|
||||
mHeaderTitle = mSearchScrollView.findViewById(R.id.title);
|
||||
|
||||
onRecommendedWidgetsBound();
|
||||
onWidgetsBound();
|
||||
setUpEducationViewsIfNeeded();
|
||||
}
|
||||
|
||||
protected void setupViews() {
|
||||
mSearchScrollView = findViewById(R.id.search_and_recommendations_container);
|
||||
mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view));
|
||||
mNoWidgetsView = findViewById(R.id.no_widgets_text);
|
||||
mFastScroller = findViewById(R.id.fast_scroller);
|
||||
if (mIsTwoPane) {
|
||||
mFastScroller.setVisibility(GONE);
|
||||
}
|
||||
mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
|
||||
|
||||
mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view));
|
||||
mAdapters.get(AdapterHolder.SEARCH).setup(findViewById(R.id.search_widgets_list_view));
|
||||
if (mHasWorkProfile) {
|
||||
@@ -294,78 +256,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mViewPager = null;
|
||||
}
|
||||
|
||||
mNoWidgetsView = findViewById(R.id.no_widgets_text);
|
||||
|
||||
mSearchScrollView = findViewById(R.id.search_and_recommendations_container);
|
||||
mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view));
|
||||
|
||||
mRecommendedWidgetsTable = mIsTwoPane
|
||||
? mContent.findViewById(R.id.recommended_widget_table)
|
||||
: mSearchScrollView.findViewById(R.id.recommended_widget_table);
|
||||
|
||||
mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
|
||||
mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
|
||||
|
||||
// Add suggested widgets.
|
||||
if (mIsTwoPane) {
|
||||
mSuggestedWidgetsContainer = mSearchScrollView.findViewById(R.id.suggestions_header);
|
||||
|
||||
// Inflate the suggestions header.
|
||||
mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate(
|
||||
R.layout.widgets_list_row_header_two_pane,
|
||||
mSuggestedWidgetsContainer,
|
||||
false);
|
||||
mSuggestedWidgetsHeader.setExpanded(true);
|
||||
|
||||
PackageItemInfo packageItemInfo = new PackageItemInfo(
|
||||
/* packageName= */ SUGGESTIONS_PACKAGE_NAME,
|
||||
Process.myUserHandle()) {
|
||||
@Override
|
||||
public boolean usingLowResIcon() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
packageItemInfo.title = getContext().getString(R.string.suggested_widgets_header_title);
|
||||
WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create(
|
||||
packageItemInfo,
|
||||
getContext().getString(R.string.suggested_widgets_header_title),
|
||||
mActivityContext.getPopupDataProvider().getRecommendedWidgets())
|
||||
.withWidgetListShown();
|
||||
|
||||
mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
|
||||
mSuggestedWidgetsHeader.setIcon(
|
||||
getContext().getDrawable(R.drawable.widget_suggestions_icon));
|
||||
mSuggestedWidgetsHeader.setOnClickListener(view -> {
|
||||
mSuggestedWidgetsHeader.setExpanded(true);
|
||||
resetExpandedHeaders();
|
||||
mRightPane.removeAllViews();
|
||||
mRightPane.addView(mRecommendedWidgetsTable);
|
||||
});
|
||||
mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
|
||||
}
|
||||
|
||||
mTabBar = mSearchScrollView.findViewById(R.id.tabs);
|
||||
mSearchBarContainer = mSearchScrollView.findViewById(R.id.search_bar_container);
|
||||
mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar);
|
||||
mHeaderTitle = mIsTwoPane
|
||||
? mContent.findViewById(R.id.title)
|
||||
: mSearchScrollView.findViewById(R.id.title);
|
||||
mRightPane = mIsTwoPane ? mContent.findViewById(R.id.right_pane) : null;
|
||||
if (mRightPane != null) {
|
||||
mRightPane.setOutlineProvider(mViewOutlineProviderRightPane);
|
||||
}
|
||||
mRightPaneScrollView = mIsTwoPane
|
||||
? mContent.findViewById(R.id.right_pane_scroll_view) : null;
|
||||
|
||||
mWidgetsListTableViewHolderBinder =
|
||||
new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this);
|
||||
onRecommendedWidgetsBound();
|
||||
onWidgetsBound();
|
||||
|
||||
mSearchBar.initialize(
|
||||
mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
|
||||
|
||||
setUpEducationViewsIfNeeded();
|
||||
}
|
||||
|
||||
private void setDeviceManagementResources() {
|
||||
@@ -380,14 +276,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
@Override
|
||||
public void onActivePageChanged(int currentActivePage) {
|
||||
|
||||
// if the current active page changes to personal or work we set suggestions
|
||||
// to be the selected widget
|
||||
if (mIsTwoPane && mSuggestedWidgetsHeader != null
|
||||
&& (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB)) {
|
||||
mSuggestedWidgetsHeader.callOnClick();
|
||||
}
|
||||
|
||||
AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
|
||||
WidgetsRecyclerView currentRecyclerView =
|
||||
mAdapters.get(currentActivePage).mWidgetsRecyclerView;
|
||||
@@ -415,14 +303,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
|
||||
protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
|
||||
// The first item is always an empty space entry. Look for any more items.
|
||||
boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
|
||||
|
||||
if (mIsTwoPane) {
|
||||
mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
|
||||
|
||||
if (adapterHolder.mAdapterType == AdapterHolder.SEARCH) {
|
||||
@@ -468,9 +351,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
super.onAttachedToWindow();
|
||||
mActivityContext.getAppWidgetHolder().addProviderChangeListener(this);
|
||||
notifyWidgetProvidersChanged();
|
||||
if (!mIsTwoPane) {
|
||||
onRecommendedWidgetsBound();
|
||||
}
|
||||
onRecommendedWidgetsBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -558,14 +439,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
private boolean updateMaxSpansPerRow() {
|
||||
if (getMeasuredWidth() == 0) return false;
|
||||
|
||||
View content = mHasWorkProfile
|
||||
? mViewPager
|
||||
: mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
|
||||
if (mIsTwoPane && mRightPane != null) {
|
||||
content = mRightPane;
|
||||
}
|
||||
|
||||
@Px int maxHorizontalSpan = content.getMeasuredWidth() - (2 * mContentHorizontalMargin);
|
||||
@Px int maxHorizontalSpan = getContentView().getMeasuredWidth()
|
||||
- (2 * mContentHorizontalMargin);
|
||||
if (mMaxSpanPerRow != maxHorizontalSpan) {
|
||||
mMaxSpanPerRow = maxHorizontalSpan;
|
||||
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
|
||||
@@ -582,6 +457,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
return false;
|
||||
}
|
||||
|
||||
protected View getContentView() {
|
||||
return mHasWorkProfile
|
||||
? mViewPager
|
||||
: mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int width = r - l;
|
||||
@@ -656,19 +537,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
public void onSearchResults(List<WidgetsListBaseEntry> entries) {
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
|
||||
updateRecyclerViewVisibility(mAdapters.get(AdapterHolder.SEARCH));
|
||||
if (mIsTwoPane) {
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.selectFirstHeaderEntry();
|
||||
}
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
|
||||
}
|
||||
|
||||
private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
|
||||
protected void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
|
||||
mIsInSearchMode = isInSearchMode;
|
||||
if (isInSearchMode) {
|
||||
mRecommendedWidgetsTable.setVisibility(GONE);
|
||||
if (mIsTwoPane) {
|
||||
mSuggestedWidgetsContainer.setVisibility(GONE);
|
||||
}
|
||||
if (mHasWorkProfile) {
|
||||
mViewPager.setVisibility(GONE);
|
||||
mTabBar.setVisibility(GONE);
|
||||
@@ -680,10 +555,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mNoWidgetsView.setVisibility(GONE);
|
||||
} else {
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
|
||||
if (mIsTwoPane) {
|
||||
mSuggestedWidgetsContainer.setVisibility(VISIBLE);
|
||||
mSuggestedWidgetsHeader.callOnClick();
|
||||
}
|
||||
// Visibility of recommended widgets, recycler views and headers are handled in methods
|
||||
// below.
|
||||
onRecommendedWidgetsBound();
|
||||
@@ -691,7 +562,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
}
|
||||
}
|
||||
|
||||
private void resetExpandedHeaders() {
|
||||
protected void resetExpandedHeaders() {
|
||||
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader();
|
||||
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
|
||||
}
|
||||
@@ -713,14 +584,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mNoWidgetsView.getText().length(), noWidgetsViewTextBounds);
|
||||
noWidgetsViewHeight = noWidgetsViewTextBounds.height();
|
||||
}
|
||||
doMeasure(
|
||||
makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx,
|
||||
MeasureSpec.EXACTLY),
|
||||
makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
|
||||
MeasureSpec.EXACTLY));
|
||||
float maxTableHeight = mIsTwoPane ? Float.MAX_VALUE : (mContent.getMeasuredHeight()
|
||||
- mTabsHeight - getHeaderViewHeight()
|
||||
- noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
|
||||
if (!isTwoPane()) {
|
||||
doMeasure(
|
||||
makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx,
|
||||
MeasureSpec.EXACTLY),
|
||||
makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
|
||||
MeasureSpec.EXACTLY));
|
||||
}
|
||||
float maxTableHeight = getMaxTableHeight(noWidgetsViewHeight);
|
||||
|
||||
List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
|
||||
WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(
|
||||
@@ -732,13 +603,21 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mRecommendedWidgetsTable.setRecommendedWidgets(
|
||||
recommendedWidgetsInTable, maxTableHeight);
|
||||
} else {
|
||||
mRecommendedWidgetsTable.setVisibility(GONE);
|
||||
if (mSuggestedWidgetsContainer != null) {
|
||||
mSuggestedWidgetsContainer.setVisibility(GONE);
|
||||
}
|
||||
hideRecommendations();
|
||||
}
|
||||
}
|
||||
|
||||
protected void hideRecommendations() {
|
||||
mRecommendedWidgetsTable.setVisibility(GONE);
|
||||
}
|
||||
|
||||
protected float getMaxTableHeight(float noWidgetsViewHeight) {
|
||||
return (mContent.getMeasuredHeight()
|
||||
- mTabsHeight - getHeaderViewHeight()
|
||||
- noWidgetsViewHeight)
|
||||
* RECOMMENDATION_TABLE_HEIGHT_RATIO;
|
||||
}
|
||||
|
||||
private void open(boolean animate) {
|
||||
if (animate) {
|
||||
if (getPopupContainer().getInsets().bottom > 0) {
|
||||
@@ -779,37 +658,49 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
@Override
|
||||
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
// Disable swipe down when recycler view is scrolling or scroll view is scrolling
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mNoIntercept = false;
|
||||
WidgetsRecyclerView recyclerView = getRecyclerView();
|
||||
RecyclerViewFastScroller scroller = recyclerView.getScrollbar();
|
||||
|
||||
if (scroller.getThumbOffsetY() >= 0
|
||||
&& getPopupContainer().isEventOverView(scroller, ev)) {
|
||||
mNoIntercept = true;
|
||||
} else if (getPopupContainer().isEventOverView(recyclerView, ev)) {
|
||||
mNoIntercept = !recyclerView.shouldContainerScroll(ev, getPopupContainer());
|
||||
} else if (mIsTwoPane && mRightPaneScrollView != null
|
||||
&& getPopupContainer().isEventOverView(mRightPaneScrollView, ev)) {
|
||||
mNoIntercept = mRightPaneScrollView.canScrollVertically(-1);
|
||||
}
|
||||
mNoIntercept = shouldScroll(ev);
|
||||
if (mSearchBar.isSearchBarFocused()
|
||||
&& !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) {
|
||||
mSearchBar.clearSearchBarFocus();
|
||||
}
|
||||
}
|
||||
|
||||
return super.onControllerInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
protected boolean shouldScroll(MotionEvent ev) {
|
||||
boolean intercept = false;
|
||||
WidgetsRecyclerView recyclerView = getRecyclerView();
|
||||
RecyclerViewFastScroller scroller = recyclerView.getScrollbar();
|
||||
// Disable swipe down when recycler view is scrolling
|
||||
if (scroller.getThumbOffsetY() >= 0 && getPopupContainer().isEventOverView(scroller, ev)) {
|
||||
intercept = true;
|
||||
} else if (getPopupContainer().isEventOverView(recyclerView, ev)) {
|
||||
intercept = !recyclerView.shouldContainerScroll(ev, getPopupContainer());
|
||||
}
|
||||
return intercept;
|
||||
}
|
||||
|
||||
/** Shows the {@link WidgetsFullSheet} on the launcher. */
|
||||
public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
|
||||
WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
|
||||
.inflate(LARGE_SCREEN_WIDGET_PICKER.get()
|
||||
&& launcher.getDeviceProfile().isTablet
|
||||
&& launcher.getDeviceProfile().isLandscape
|
||||
? R.layout.widgets_full_sheet_large_screen
|
||||
: R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
|
||||
boolean isTwoPane = LARGE_SCREEN_WIDGET_PICKER.get()
|
||||
&& launcher.getDeviceProfile().isTablet
|
||||
&& launcher.getDeviceProfile().isLandscape;
|
||||
|
||||
WidgetsFullSheet sheet;
|
||||
if (isTwoPane) {
|
||||
sheet = (WidgetsTwoPaneSheet) launcher.getLayoutInflater().inflate(
|
||||
R.layout.widgets_full_sheet_large_screen,
|
||||
launcher.getDragLayer(),
|
||||
false);
|
||||
} else {
|
||||
sheet = (WidgetsFullSheet) launcher.getLayoutInflater().inflate(
|
||||
R.layout.widgets_full_sheet,
|
||||
launcher.getDragLayer(),
|
||||
false);
|
||||
}
|
||||
|
||||
sheet.attachToContainer();
|
||||
sheet.mIsOpen = true;
|
||||
sheet.open(animate);
|
||||
@@ -973,7 +864,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|| Utilities.isRunningInTestHarness();
|
||||
}
|
||||
|
||||
private void setUpEducationViewsIfNeeded() {
|
||||
protected void setUpEducationViewsIfNeeded() {
|
||||
if (!hasSeenEducationDialog()) {
|
||||
postDelayed(() -> {
|
||||
WidgetsEduView eduDialog = showEducationDialog();
|
||||
@@ -991,14 +882,18 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isTwoPane() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** A holder class for holding adapters & their corresponding recycler view. */
|
||||
private final class AdapterHolder {
|
||||
final class AdapterHolder {
|
||||
static final int PRIMARY = 0;
|
||||
static final int WORK = 1;
|
||||
static final int SEARCH = 2;
|
||||
|
||||
private final int mAdapterType;
|
||||
private final WidgetsListAdapter mWidgetsListAdapter;
|
||||
final WidgetsListAdapter mWidgetsListAdapter;
|
||||
private final DefaultItemAnimator mWidgetsListItemAnimator;
|
||||
|
||||
private WidgetsRecyclerView mWidgetsRecyclerView;
|
||||
@@ -1006,43 +901,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
AdapterHolder(int adapterType) {
|
||||
mAdapterType = adapterType;
|
||||
Context context = getContext();
|
||||
HeaderChangeListener headerChangeListener = new HeaderChangeListener() {
|
||||
@Override
|
||||
public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
|
||||
WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
|
||||
.getSelectedAppWidgets(selectedHeader);
|
||||
|
||||
if (contentEntry == null || mRightPane == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSuggestedWidgetsHeader != null) {
|
||||
mSuggestedWidgetsHeader.setExpanded(false);
|
||||
}
|
||||
WidgetsRowViewHolder widgetsRowViewHolder =
|
||||
mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane);
|
||||
mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
|
||||
contentEntry,
|
||||
ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
|
||||
Collections.EMPTY_LIST);
|
||||
widgetsRowViewHolder.mDataCallback = data -> {
|
||||
mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
|
||||
contentEntry.withMaxSpanSize(mMaxSpanPerRow),
|
||||
ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
|
||||
Collections.singletonList(data));
|
||||
};
|
||||
|
||||
mRightPane.removeAllViews();
|
||||
mRightPane.addView(widgetsRowViewHolder.itemView);
|
||||
}
|
||||
};
|
||||
mWidgetsListAdapter = new WidgetsListAdapter(
|
||||
context,
|
||||
LayoutInflater.from(context),
|
||||
this::getEmptySpaceHeight,
|
||||
/* iconClickListener= */ WidgetsFullSheet.this,
|
||||
/* iconLongClickListener= */ WidgetsFullSheet.this,
|
||||
mIsTwoPane ? headerChangeListener : null);
|
||||
isTwoPane());
|
||||
mWidgetsListAdapter.setHasStableIds(true);
|
||||
switch (mAdapterType) {
|
||||
case PRIMARY:
|
||||
@@ -1073,7 +939,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mWidgetsRecyclerView.bindFastScrollbar(mFastScroller);
|
||||
mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator);
|
||||
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
|
||||
if (!mIsTwoPane) {
|
||||
if (!isTwoPane()) {
|
||||
mWidgetsRecyclerView.setEdgeEffectFactory(
|
||||
((SpringRelativeLayout) mContent).createEdgeEffectFactory());
|
||||
}
|
||||
@@ -1086,15 +952,4 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a listener for when the selected header gets changed in the left pane.
|
||||
*/
|
||||
public interface HeaderChangeListener {
|
||||
/**
|
||||
* Sets the right pane to have the widgets for the currently selected header from
|
||||
* the left pane.
|
||||
*/
|
||||
void onHeaderChanged(@NonNull PackageUserKey selectedHeader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
|
||||
private final WidgetListBaseRowEntryComparator mRowComparator =
|
||||
new WidgetListBaseRowEntryComparator();
|
||||
@Nullable private final WidgetsFullSheet.HeaderChangeListener mHeaderChangeListener;
|
||||
@Nullable private WidgetsTwoPaneSheet.HeaderChangeListener mHeaderChangeListener;
|
||||
|
||||
private final List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
|
||||
private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
|
||||
@@ -107,8 +107,7 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
|
||||
IntSupplier emptySpaceHeightProvider, OnClickListener iconClickListener,
|
||||
OnLongClickListener iconLongClickListener,
|
||||
WidgetsFullSheet.HeaderChangeListener headerChangeListener) {
|
||||
mHeaderChangeListener = headerChangeListener;
|
||||
boolean isTwoPane) {
|
||||
mContext = context;
|
||||
mMaxHorizontalSpan = WidgetSizes.getWidgetSizePx(
|
||||
ActivityContext.lookupContext(context).getDeviceProfile(),
|
||||
@@ -122,12 +121,17 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
VIEW_TYPE_WIDGETS_HEADER,
|
||||
new WidgetsListHeaderViewHolderBinder(
|
||||
layoutInflater, /* onHeaderClickListener= */ this,
|
||||
headerChangeListener != null));
|
||||
isTwoPane));
|
||||
mViewHolderBinders.put(
|
||||
VIEW_TYPE_WIDGETS_SPACE,
|
||||
new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
|
||||
}
|
||||
|
||||
public void setHeaderChangeListener(WidgetsTwoPaneSheet.HeaderChangeListener
|
||||
headerChangeListener) {
|
||||
mHeaderChangeListener = headerChangeListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
mRecyclerView = recyclerView;
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.widget.picker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Outline;
|
||||
import android.os.Process;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.recyclerview.ViewHolderBinder;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListContentEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Popup for showing the full list of available widgets with a two-pane layout.
|
||||
*/
|
||||
public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
|
||||
private static final int PERSONAL_TAB = 0;
|
||||
private static final int WORK_TAB = 1;
|
||||
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
|
||||
|
||||
private LinearLayout mSuggestedWidgetsContainer;
|
||||
private WidgetsListHeader mSuggestedWidgetsHeader;
|
||||
private LinearLayout mRightPane;
|
||||
|
||||
private FrameLayout mRightPaneScrollView;
|
||||
private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
|
||||
|
||||
private final ViewOutlineProvider mViewOutlineProviderRightPane = new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRoundRect(
|
||||
0,
|
||||
0,
|
||||
view.getMeasuredWidth(),
|
||||
view.getMeasuredHeight() - getResources().getDimensionPixelSize(
|
||||
R.dimen.widget_list_horizontal_margin_large_screen),
|
||||
view.getResources().getDimensionPixelSize(
|
||||
R.dimen.widget_list_top_bottom_corner_radius)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public WidgetsTwoPaneSheet(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupSheet() {
|
||||
// Set the header change listener in the adapter
|
||||
mAdapters.get(AdapterHolder.PRIMARY)
|
||||
.mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener());
|
||||
mAdapters.get(AdapterHolder.WORK)
|
||||
.mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener());
|
||||
mAdapters.get(AdapterHolder.SEARCH)
|
||||
.mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener());
|
||||
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
|
||||
|
||||
int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view_large_screen
|
||||
: R.layout.widgets_full_sheet_recyclerview_large_screen;
|
||||
layoutInflater.inflate(contentLayoutRes, findViewById(R.id.recycler_view_container), true);
|
||||
|
||||
setupViews();
|
||||
|
||||
mWidgetsListTableViewHolderBinder =
|
||||
new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this);
|
||||
mRecommendedWidgetsTable = mContent.findViewById(R.id.recommended_widget_table);
|
||||
mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
|
||||
mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
|
||||
mHeaderTitle = mContent.findViewById(R.id.title);
|
||||
mRightPane = mContent.findViewById(R.id.right_pane);
|
||||
mRightPane.setOutlineProvider(mViewOutlineProviderRightPane);
|
||||
mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
|
||||
mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
|
||||
setupSuggestedWidgets(layoutInflater);
|
||||
onRecommendedWidgetsBound();
|
||||
onWidgetsBound();
|
||||
setUpEducationViewsIfNeeded();
|
||||
|
||||
// Set the fast scroller as not visible for two pane layout.
|
||||
mFastScroller.setVisibility(GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hideRecommendations() {
|
||||
super.hideRecommendations();
|
||||
mSuggestedWidgetsContainer.setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void setupSuggestedWidgets(LayoutInflater layoutInflater) {
|
||||
// Add suggested widgets.
|
||||
mSuggestedWidgetsContainer = mSearchScrollView.findViewById(R.id.suggestions_header);
|
||||
|
||||
// Inflate the suggestions header.
|
||||
mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate(
|
||||
R.layout.widgets_list_row_header_two_pane,
|
||||
mSuggestedWidgetsContainer,
|
||||
false);
|
||||
mSuggestedWidgetsHeader.setExpanded(true);
|
||||
|
||||
PackageItemInfo packageItemInfo = new PackageItemInfo(
|
||||
/* packageName= */ SUGGESTIONS_PACKAGE_NAME,
|
||||
Process.myUserHandle()) {
|
||||
@Override
|
||||
public boolean usingLowResIcon() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
packageItemInfo.title = getContext().getString(R.string.suggested_widgets_header_title);
|
||||
WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create(
|
||||
packageItemInfo,
|
||||
getContext().getString(R.string.suggested_widgets_header_title),
|
||||
mActivityContext.getPopupDataProvider().getRecommendedWidgets())
|
||||
.withWidgetListShown();
|
||||
|
||||
mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
|
||||
mSuggestedWidgetsHeader.setIcon(
|
||||
getContext().getDrawable(R.drawable.widget_suggestions_icon));
|
||||
mSuggestedWidgetsHeader.setOnClickListener(view -> {
|
||||
mSuggestedWidgetsHeader.setExpanded(true);
|
||||
resetExpandedHeaders();
|
||||
mRightPane.removeAllViews();
|
||||
mRightPane.addView(mRecommendedWidgetsTable);
|
||||
});
|
||||
mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getMaxTableHeight(float noWidgetsViewHeight) {
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivePageChanged(int currentActivePage) {
|
||||
// if the current active page changes to personal or work we set suggestions
|
||||
// to be the selected widget
|
||||
if (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB) {
|
||||
mSuggestedWidgetsHeader.callOnClick();
|
||||
}
|
||||
|
||||
super.onActivePageChanged(currentActivePage);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
|
||||
// The first item is always an empty space entry. Look for any more items.
|
||||
boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
|
||||
|
||||
mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
|
||||
|
||||
super.updateRecyclerViewVisibility(adapterHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
mSuggestedWidgetsContainer.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchResults(List<WidgetsListBaseEntry> entries) {
|
||||
super.onSearchResults(entries);
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.selectFirstHeaderEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldScroll(MotionEvent ev) {
|
||||
return getPopupContainer().isEventOverView(mRightPaneScrollView, ev)
|
||||
? mRightPaneScrollView.canScrollVertically(-1)
|
||||
: super.shouldScroll(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
|
||||
if (isInSearchMode) {
|
||||
mSuggestedWidgetsContainer.setVisibility(GONE);
|
||||
} else {
|
||||
mSuggestedWidgetsContainer.setVisibility(VISIBLE);
|
||||
mSuggestedWidgetsHeader.callOnClick();
|
||||
}
|
||||
super.setViewVisibilityBasedOnSearch(isInSearchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getContentView() {
|
||||
return mRightPane;
|
||||
}
|
||||
|
||||
private HeaderChangeListener getHeaderChangeListener() {
|
||||
return new HeaderChangeListener() {
|
||||
@Override
|
||||
public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
|
||||
WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
|
||||
.getSelectedAppWidgets(selectedHeader);
|
||||
|
||||
if (contentEntry == null || mRightPane == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSuggestedWidgetsHeader != null) {
|
||||
mSuggestedWidgetsHeader.setExpanded(false);
|
||||
}
|
||||
WidgetsRowViewHolder widgetsRowViewHolder =
|
||||
mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane);
|
||||
mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
|
||||
contentEntry,
|
||||
ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
|
||||
Collections.EMPTY_LIST);
|
||||
widgetsRowViewHolder.mDataCallback = data -> {
|
||||
mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
|
||||
contentEntry,
|
||||
ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
|
||||
Collections.singletonList(data));
|
||||
};
|
||||
mRightPane.removeAllViews();
|
||||
mRightPane.addView(widgetsRowViewHolder.itemView);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isTwoPane() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a listener for when the selected header gets changed in the left pane.
|
||||
*/
|
||||
public interface HeaderChangeListener {
|
||||
/**
|
||||
* Sets the right pane to have the widgets for the currently selected header from
|
||||
* the left pane.
|
||||
*/
|
||||
void onHeaderChanged(@NonNull PackageUserKey selectedHeader);
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,8 @@ public class WorkProfileTest extends AbstractLauncherUiTest {
|
||||
}
|
||||
updateWorkProfileSetupSuccessful("am start-user", output);
|
||||
|
||||
Log.d(WORK_TAB_MISSING, "workProfileSuccessful? " + mWorkProfileSetupSuccessful);
|
||||
Log.d(WORK_TAB_MISSING, "workProfileSuccessful? " + mWorkProfileSetupSuccessful +
|
||||
" shellCmd: " + logStr);
|
||||
if (!mWorkProfileSetupSuccessful) {
|
||||
return; // no need to setup launcher since all tests will skip.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user