Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ebf2cdd0 authored by Vinit Nayak's avatar Vinit Nayak
Browse files

Show Digital Wellbeing Banners for split tasks

Fixes: 199936292
Change-Id: I38743d58f53a65ef717b1babc21ad1df2dc840a4
parent 9b3b2b9c
Loading
Loading
Loading
Loading
+120 −27
Original line number Diff line number Diff line
@@ -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);
    }

    private String getText(long remainingTime) {
        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,
                getRoundedUpToMinuteReadableDuration(remainingTime));
                    readableDuration);
        }

        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<Float, Float> 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;
+22 −4
Original line number Diff line number Diff line
@@ -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);
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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;
+40 −0
Original line number Diff line number Diff line
@@ -309,6 +309,46 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler {
        return new PointF(margin, 0);
    }

    @Override
    public Pair<Float, Float> 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
+4 −0
Original line number Diff line number Diff line
@@ -191,6 +191,10 @@ public interface PagedOrientationHandler {
     */
    PointF getAdditionalInsetForTaskMenu(float margin);

    Pair<Float, Float> 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();
Loading