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

Commit 8d25a0a3 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Show Digital Wellbeing Banners for split tasks" into sc-v2-dev

parents 20102c3d ebf2cdd0
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