From ebf2cdd0f141bc1e82ca260de40a76674aed95c1 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Mon, 13 Dec 2021 15:28:39 -0800 Subject: [PATCH] Show Digital Wellbeing Banners for split tasks Fixes: 199936292 Change-Id: I38743d58f53a65ef717b1babc21ad1df2dc840a4 --- .../views/DigitalWellBeingToast.java | 147 ++++++++++++++---- .../quickstep/views/GroupedTaskView.java | 26 +++- .../com/android/quickstep/views/TaskView.java | 2 +- .../touch/LandscapePagedViewHandler.java | 40 +++++ .../touch/PagedOrientationHandler.java | 4 + .../touch/PortraitPagedViewHandler.java | 46 ++++++ .../touch/SeascapePagedViewHandler.java | 43 +++++ 7 files changed, 276 insertions(+), 32 deletions(-) diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java index dbdcf19d84..e5664c6032 100644 --- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java @@ -19,6 +19,7 @@ package com.android.quickstep.views; import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS; import static android.view.Gravity.BOTTOM; import static android.view.Gravity.CENTER_HORIZONTAL; +import static android.view.Gravity.START; import static com.android.launcher3.Utilities.prefixTextWithIcon; import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; @@ -38,26 +39,51 @@ import android.icu.util.MeasureUnit; import android.os.Build; import android.os.UserHandle; import android.util.Log; +import android.util.Pair; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; import com.android.systemui.shared.recents.model.Task; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.time.Duration; import java.util.Locale; @TargetApi(Build.VERSION_CODES.Q) public final class DigitalWellBeingToast { + + private static final float THRESHOLD_LEFT_ICON_ONLY = 0.4f; + private static final float THRESHOLD_RIGHT_ICON_ONLY = 0.6f; + + /** Will span entire width of taskView with full text */ + private static final int SPLIT_BANNER_FULLSCREEN = 0; + /** Used for grid task view, only showing icon and time */ + private static final int SPLIT_GRID_BANNER_LARGE = 1; + /** Used for grid task view, only showing icon */ + private static final int SPLIT_GRID_BANNER_SMALL = 2; + @IntDef(value = { + SPLIT_BANNER_FULLSCREEN, + SPLIT_GRID_BANNER_LARGE, + SPLIT_GRID_BANNER_SMALL, + }) + @Retention(RetentionPolicy.SOURCE) + @interface SPLIT_BANNER_CONFIG{} + static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS); static final int MINUTE_MS = 60000; @@ -74,7 +100,16 @@ public final class DigitalWellBeingToast { private View mBanner; private ViewOutlineProvider mOldBannerOutlineProvider; private float mBannerOffsetPercentage; - private float mVerticalOffset = 0f; + /** + * Clips rect provided by {@link #mOldBannerOutlineProvider} when in the model state to + * hide this banner as the taskView scales up and down + */ + private float mModalOffset = 0f; + @Nullable + private StagedSplitBounds mStagedSplitBounds; + private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN; + private float mSplitOffsetTranslationY; + private float mSplitOffsetTranslationX; public DigitalWellBeingToast(BaseDraggingActivity activity, TaskView taskView) { mActivity = activity; @@ -103,7 +138,7 @@ public final class DigitalWellBeingToast { } public String getText() { - return getText(mAppRemainingTimeMs); + return getText(mAppRemainingTimeMs, false /* forContentDesc */); } public boolean hasLimit() { @@ -138,6 +173,31 @@ public final class DigitalWellBeingToast { }); } + public void setSplitConfiguration(StagedSplitBounds stagedSplitBounds) { + mStagedSplitBounds = stagedSplitBounds; + if (mStagedSplitBounds == null || + !mActivity.getDeviceProfile().overviewShowAsGrid || + mTaskView.isFocusedTask()) { + mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN; + return; + } + + // For portrait grid only height of task changes, not width. So we keep the text the same + if (!mActivity.getDeviceProfile().isLandscape) { + mSplitBannerConfig = SPLIT_GRID_BANNER_LARGE; + return; + } + + // For landscape grid, for 30% width we only show icon, otherwise show icon and time + if (mTask.key.id == mStagedSplitBounds.leftTopTaskId) { + mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ? + SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE; + } else { + mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ? + SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE; + } + } + private String getReadableDuration( Duration duration, FormatWidth formatWidthHourAndMinute, @@ -181,30 +241,33 @@ public final class DigitalWellBeingToast { .formatMeasures(new Measure(0, MeasureUnit.MINUTE)); } - private String getReadableDuration( - Duration duration, - FormatWidth formatWidthHourAndMinute, - @StringRes int durationLessThanOneMinuteStringId) { - return getReadableDuration( - duration, - formatWidthHourAndMinute, - durationLessThanOneMinuteStringId, - /* forceFormatWidth= */ false); - } - - private String getRoundedUpToMinuteReadableDuration(long remainingTime) { + /** + * Returns text to show for the banner depending on {@link #mSplitBannerConfig} + * If {@param forContentDesc} is {@code true}, this will always return the full + * string corresponding to {@link #SPLIT_BANNER_FULLSCREEN} + */ + private String getText(long remainingTime, boolean forContentDesc) { final Duration duration = Duration.ofMillis( remainingTime > MINUTE_MS ? (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS : remainingTime); - return getReadableDuration( - duration, FormatWidth.NARROW, R.string.shorter_duration_less_than_one_minute); - } + String readableDuration = getReadableDuration(duration, + FormatWidth.NARROW, + R.string.shorter_duration_less_than_one_minute, + false /* forceFormatWidth */); + if (forContentDesc || mSplitBannerConfig == SPLIT_BANNER_FULLSCREEN) { + return mActivity.getString( + R.string.time_left_for_app, + readableDuration); + } - private String getText(long remainingTime) { - return mActivity.getString( - R.string.time_left_for_app, - getRoundedUpToMinuteReadableDuration(remainingTime)); + if (mSplitBannerConfig == SPLIT_GRID_BANNER_SMALL) { + // show no text + return ""; + } else { // SPLIT_GRID_BANNER_LARGE + // only show time + return readableDuration; + } } public void openAppUsageSettings(View view) { @@ -232,7 +295,7 @@ public final class DigitalWellBeingToast { mActivity.getString( R.string.task_contents_description_with_remaining_time, task.titleDescription, - getText(appRemainingTimeMs)) : + getText(appRemainingTimeMs, true /* forContentDesc */)) : task.titleDescription; } @@ -261,10 +324,18 @@ public final class DigitalWellBeingToast { private void setupAndAddBanner() { FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mBanner.getLayoutParams(); - layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL; + DeviceProfile deviceProfile = mActivity.getDeviceProfile(); layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams) mTaskView.getThumbnail().getLayoutParams()).bottomMargin; - mBanner.setTranslationY(mBannerOffsetPercentage * mBanner.getHeight()); + PagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler(); + Pair translations = orientationHandler + .setDwbLayoutParamsAndGetTranslations(mTaskView.getMeasuredWidth(), + mTaskView.getMeasuredHeight(), mStagedSplitBounds, deviceProfile, + mTaskView.getThumbnails(), mTask.key.id, mBanner); + mSplitOffsetTranslationX = translations.first; + mSplitOffsetTranslationY = translations.second; + updateTranslationY(); + updateTranslationX(); mTaskView.addView(mBanner); } @@ -274,7 +345,9 @@ public final class DigitalWellBeingToast { @Override public void getOutline(View view, Outline outline) { mOldBannerOutlineProvider.getOutline(view, outline); - outline.offset(0, Math.round(-view.getTranslationY() + mVerticalOffset)); + float verticalTranslation = -view.getTranslationY() + mModalOffset + + mSplitOffsetTranslationY; + outline.offset(0, Math.round(verticalTranslation)); } }); mBanner.setClipToOutline(true); @@ -282,13 +355,33 @@ public final class DigitalWellBeingToast { void updateBannerOffset(float offsetPercentage, float verticalOffset) { if (mBanner != null && mBannerOffsetPercentage != offsetPercentage) { - mVerticalOffset = verticalOffset; + mModalOffset = verticalOffset; mBannerOffsetPercentage = offsetPercentage; - mBanner.setTranslationY(offsetPercentage * mBanner.getHeight() + mVerticalOffset); + updateTranslationY(); mBanner.invalidateOutline(); } } + private void updateTranslationY() { + if (mBanner == null) { + return; + } + + mBanner.setTranslationY( + (mBannerOffsetPercentage * mBanner.getHeight()) + + mModalOffset + + mSplitOffsetTranslationY + ); + } + + private void updateTranslationX() { + if (mBanner == null) { + return; + } + + mBanner.setTranslationX(mSplitOffsetTranslationX); + } + void setBannerColorTint(int color, float amount) { if (mBanner == null) { return; diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index b215ef1f54..7e4f9d0abc 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -50,17 +50,20 @@ public class GroupedTaskView extends TaskView { private final float[] mIcon2CenterCoords = new float[2]; private TransformingTouchDelegate mIcon2TouchDelegate; @Nullable private StagedSplitBounds mSplitBoundsConfig; + private final DigitalWellBeingToast mDigitalWellBeingToast2; + public GroupedTaskView(Context context) { - super(context); + this(context, null); } public GroupedTaskView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } public GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + mDigitalWellBeingToast2 = new DigitalWellBeingToast(mActivity, this); } @Override @@ -102,7 +105,9 @@ public class GroupedTaskView extends TaskView { mIconLoadRequest2 = iconCache.updateIconInBackground(mSecondaryTask, (task) -> { setIcon(mIconView2, task.icon); - // TODO(199936292) Digital Wellbeing for individual tasks? + mDigitalWellBeingToast2.initialize(mSecondaryTask); + mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig); + mDigitalWellBeingToast.setSplitConfiguration(mSplitBoundsConfig); }); } } else { @@ -262,6 +267,19 @@ public class GroupedTaskView extends TaskView { @Override protected void setIconAndDimTransitionProgress(float progress, boolean invert) { super.setIconAndDimTransitionProgress(progress, invert); - mIconView2.setAlpha(mIconView.getAlpha()); + // Value set by super call + float scale = mIconView.getAlpha(); + mIconView2.setAlpha(scale); + mDigitalWellBeingToast2.updateBannerOffset(1f - scale, + mCurrentFullscreenParams.mCurrentDrawnInsets.top + + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom); + } + + @Override + public void setColorTint(float amount, int tintColor) { + super.setColorTint(amount, tintColor); + mIconView2.setIconColorTint(tintColor, amount); + mSnapshotView2.setDimAlpha(amount); + mDigitalWellBeingToast2.setBannerColorTint(tintColor, amount); } } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index e33d6501f3..a71e558119 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -366,7 +366,7 @@ public class TaskView extends FrameLayout implements Reusable { protected Task mTask; protected TaskThumbnailView mSnapshotView; protected IconView mIconView; - private final DigitalWellBeingToast mDigitalWellBeingToast; + protected final DigitalWellBeingToast mDigitalWellBeingToast; private float mFullscreenProgress; private float mGridProgress; private float mNonGridScale = 1; diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java index 9a2d6d8c54..a94ad7c2bb 100644 --- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java @@ -309,6 +309,46 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { return new PointF(margin, 0); } + @Override + public Pair setDwbLayoutParamsAndGetTranslations(int taskViewWidth, + int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile, + View[] thumbnailViews, int desiredTaskId, View banner) { + float translationX = 0; + float translationY = 0; + FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams(); + banner.setPivotX(0); + banner.setPivotY(0); + banner.setRotation(getDegreesRotated()); + translationX = banner.getHeight(); + FrameLayout.LayoutParams snapshotParams = + (FrameLayout.LayoutParams) thumbnailViews[0] + .getLayoutParams(); + bannerParams.gravity = TOP | START; + if (splitBounds == null) { + // Single, fullscreen case + bannerParams.width = taskViewHeight - snapshotParams.topMargin; + return new Pair<>(translationX, Integer.valueOf(snapshotParams.topMargin).floatValue()); + } + + // Set correct width + if (desiredTaskId == splitBounds.leftTopTaskId) { + bannerParams.width = thumbnailViews[0].getMeasuredHeight(); + } else { + bannerParams.width = thumbnailViews[1].getMeasuredHeight(); + } + + // Set translations + if (desiredTaskId == splitBounds.rightBottomTaskId) { + translationY = (snapshotParams.topMargin + taskViewHeight) + * (splitBounds.leftTaskPercent) + + (taskViewHeight * splitBounds.dividerWidthPercent); + } + if (desiredTaskId == splitBounds.leftTopTaskId) { + translationY = snapshotParams.topMargin; + } + return new Pair<>(translationX, translationY); + } + /* ---------- The following are only used by TaskViewTouchHandler. ---------- */ @Override diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java index 95168fb3d6..19c4639f1d 100644 --- a/src/com/android/launcher3/touch/PagedOrientationHandler.java +++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java @@ -191,6 +191,10 @@ public interface PagedOrientationHandler { */ PointF getAdditionalInsetForTaskMenu(float margin); + Pair setDwbLayoutParamsAndGetTranslations(int taskViewWidth, + int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile, + View[] thumbnailViews, int desiredTaskId, View banner); + // The following are only used by TaskViewTouchHandler. /** @return Either VERTICAL or HORIZONTAL. */ SingleAxisSwipeDetector.Direction getUpDownSwipeDirection(); diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index 7d70f77138..ad9f95c256 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -16,9 +16,11 @@ package com.android.launcher3.touch; +import static android.view.Gravity.BOTTOM; import static android.view.Gravity.CENTER_HORIZONTAL; import static android.view.Gravity.START; import static android.view.Gravity.TOP; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; @@ -321,6 +323,50 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { return new PointF(0, 0); } + @Override + public Pair setDwbLayoutParamsAndGetTranslations(int taskViewWidth, + int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile, + View[] thumbnailViews, int desiredTaskId, View banner) { + float translationX = 0; + float translationY = 0; + FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams(); + banner.setPivotX(0); + banner.setPivotY(0); + banner.setRotation(getDegreesRotated()); + if (splitBounds == null) { + // Single, fullscreen case + bannerParams.width = MATCH_PARENT; + bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL; + return new Pair<>(translationX, translationY); + } + + bannerParams.gravity = BOTTOM | ((deviceProfile.isLandscape) ? START : CENTER_HORIZONTAL); + + // Set correct width + if (desiredTaskId == splitBounds.leftTopTaskId) { + bannerParams.width = thumbnailViews[0].getMeasuredWidth(); + } else { + bannerParams.width = thumbnailViews[1].getMeasuredWidth(); + } + + // Set translations + if (deviceProfile.isLandscape) { + if (desiredTaskId == splitBounds.rightBottomTaskId) { + translationX = ((taskViewWidth * splitBounds.leftTaskPercent) + + (taskViewWidth * splitBounds.dividerWidthPercent)); + } + } else { + if (desiredTaskId == splitBounds.leftTopTaskId) { + FrameLayout.LayoutParams snapshotParams = + (FrameLayout.LayoutParams) thumbnailViews[0] + .getLayoutParams(); + translationY = -((taskViewHeight - snapshotParams.topMargin) + * (1f - splitBounds.topTaskPercent)); + } + } + return new Pair<>(translationX, translationY); + } + /* ---------- The following are only used by TaskViewTouchHandler. ---------- */ @Override diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java index a921f943d1..de5f99cd18 100644 --- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java @@ -16,6 +16,7 @@ package com.android.launcher3.touch; +import static android.view.Gravity.BOTTOM; import static android.view.Gravity.CENTER_VERTICAL; import static android.view.Gravity.END; import static android.view.Gravity.START; @@ -29,6 +30,7 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MA import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; +import android.util.Pair; import android.view.Surface; import android.view.View; import android.widget.FrameLayout; @@ -105,6 +107,47 @@ public class SeascapePagedViewHandler extends LandscapePagedViewHandler { return new PointF(-margin, margin); } + @Override + public Pair setDwbLayoutParamsAndGetTranslations(int taskViewWidth, + int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile, + View[] thumbnailViews, int desiredTaskId, View banner) { + float translationX = 0; + float translationY = 0; + FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams(); + banner.setPivotX(0); + banner.setPivotY(0); + banner.setRotation(getDegreesRotated()); + FrameLayout.LayoutParams snapshotParams = + (FrameLayout.LayoutParams) thumbnailViews[0] + .getLayoutParams(); + bannerParams.gravity = BOTTOM | END; + translationX = taskViewWidth - banner.getHeight(); + if (splitBounds == null) { + // Single, fullscreen case + bannerParams.width = taskViewHeight - snapshotParams.topMargin; + translationY = banner.getHeight(); + return new Pair<>(translationX, translationY); + } + + // Set correct width + if (desiredTaskId == splitBounds.leftTopTaskId) { + bannerParams.width = thumbnailViews[1].getMeasuredHeight(); + } else { + bannerParams.width = thumbnailViews[0].getMeasuredHeight(); + } + + // Set translations + if (desiredTaskId == splitBounds.rightBottomTaskId) { + translationY = -(taskViewHeight - snapshotParams.topMargin) + * (1f - splitBounds.leftTaskPercent) + + banner.getHeight(); + } + if (desiredTaskId == splitBounds.leftTopTaskId) { + translationY = banner.getHeight(); + } + return new Pair<>(translationX, translationY); + } + @Override public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) { return dp.widthPx - rect.right;