| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.launcher3.taskbar; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ValueAnimator; |
| import android.content.SharedPreferences; |
| import android.content.res.Resources; |
| import android.graphics.Outline; |
| import android.graphics.Rect; |
| import android.view.View; |
| import android.view.ViewOutlineProvider; |
| |
| import com.android.launcher3.R; |
| import com.android.launcher3.Utilities; |
| import com.android.launcher3.anim.RevealOutlineAnimation; |
| import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; |
| import com.android.launcher3.util.Executors; |
| import com.android.launcher3.util.MultiValueAlpha; |
| import com.android.quickstep.AnimatedFloat; |
| import com.android.systemui.shared.navigationbar.RegionSamplingHelper; |
| |
| /** |
| * Handles properties/data collection, then passes the results to our stashed handle View to render. |
| */ |
| public class StashedHandleViewController { |
| |
| public static final int ALPHA_INDEX_STASHED = 0; |
| public static final int ALPHA_INDEX_HOME_DISABLED = 1; |
| private static final int NUM_ALPHA_CHANNELS = 2; |
| |
| /** |
| * The SharedPreferences key for whether the stashed handle region is dark. |
| */ |
| private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY = |
| "stashed_handle_region_is_dark"; |
| |
| private final TaskbarActivityContext mActivity; |
| private final SharedPreferences mPrefs; |
| private final StashedHandleView mStashedHandleView; |
| private final int mStashedHandleWidth; |
| private final int mStashedHandleHeight; |
| private final RegionSamplingHelper mRegionSamplingHelper; |
| private final MultiValueAlpha mTaskbarStashedHandleAlpha; |
| private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat( |
| this::updateStashedHandleHintScale); |
| |
| // Initialized in init. |
| private TaskbarControllers mControllers; |
| |
| // The bounds we want to clip to in the settled state when showing the stashed handle. |
| private final Rect mStashedHandleBounds = new Rect(); |
| private float mStashedHandleRadius; |
| |
| // When the reveal animation is cancelled, we can assume it's about to create a new animation, |
| // which should start off at the same point the cancelled one left off. |
| private float mStartProgressForNextRevealAnim; |
| private boolean mWasLastRevealAnimReversed; |
| |
| public StashedHandleViewController(TaskbarActivityContext activity, |
| StashedHandleView stashedHandleView) { |
| mActivity = activity; |
| mPrefs = Utilities.getPrefs(mActivity); |
| mStashedHandleView = stashedHandleView; |
| mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, NUM_ALPHA_CHANNELS); |
| mTaskbarStashedHandleAlpha.setUpdateVisibility(true); |
| mStashedHandleView.updateHandleColor( |
| mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false), |
| false /* animate */); |
| final Resources resources = mActivity.getResources(); |
| mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width); |
| mStashedHandleHeight = resources.getDimensionPixelSize( |
| R.dimen.taskbar_stashed_handle_height); |
| mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView, |
| new RegionSamplingHelper.SamplingCallback() { |
| @Override |
| public void onRegionDarknessChanged(boolean isRegionDark) { |
| mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */); |
| mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, |
| isRegionDark).apply(); |
| } |
| |
| @Override |
| public Rect getSampledRegion(View sampledView) { |
| return mStashedHandleView.getSampledRegion(); |
| } |
| }, Executors.UI_HELPER_EXECUTOR); |
| } |
| |
| public void init(TaskbarControllers controllers) { |
| mControllers = controllers; |
| mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; |
| |
| mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(0); |
| mTaskbarStashedHandleHintScale.updateValue(1f); |
| |
| final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight(); |
| mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() { |
| @Override |
| public void getOutline(View view, Outline outline) { |
| final int stashedCenterX = view.getWidth() / 2; |
| final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2; |
| mStashedHandleBounds.set( |
| stashedCenterX - mStashedHandleWidth / 2, |
| stashedCenterY - mStashedHandleHeight / 2, |
| stashedCenterX + mStashedHandleWidth / 2, |
| stashedCenterY + mStashedHandleHeight / 2); |
| mStashedHandleView.updateSampledRegion(mStashedHandleBounds); |
| mStashedHandleRadius = view.getHeight() / 2f; |
| outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius); |
| } |
| }); |
| |
| mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> { |
| final int stashedCenterX = view.getWidth() / 2; |
| final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2; |
| |
| view.setPivotX(stashedCenterX); |
| view.setPivotY(stashedCenterY); |
| }); |
| } |
| |
| public void onDestroy() { |
| mRegionSamplingHelper.stopAndDestroy(); |
| } |
| |
| public MultiValueAlpha getStashedHandleAlpha() { |
| return mTaskbarStashedHandleAlpha; |
| } |
| |
| public AnimatedFloat getStashedHandleHintScale() { |
| return mTaskbarStashedHandleHintScale; |
| } |
| |
| /** |
| * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle |
| * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape |
| * morphs into the size of where the taskbar icons will be. |
| */ |
| public Animator createRevealAnimToIsStashed(boolean isStashed) { |
| final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider( |
| mStashedHandleRadius, mStashedHandleRadius, |
| mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds); |
| |
| boolean isReversed = !isStashed; |
| boolean changingDirection = mWasLastRevealAnimReversed != isReversed; |
| mWasLastRevealAnimReversed = isReversed; |
| if (changingDirection) { |
| mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim; |
| } |
| |
| ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView, |
| isReversed, mStartProgressForNextRevealAnim); |
| revealAnim.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction(); |
| } |
| }); |
| return revealAnim; |
| } |
| |
| public void onIsStashed(boolean isStashed) { |
| mRegionSamplingHelper.setWindowVisible(isStashed); |
| if (isStashed) { |
| mStashedHandleView.updateSampledRegion(mStashedHandleBounds); |
| mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion()); |
| } else { |
| mRegionSamplingHelper.stop(); |
| } |
| } |
| |
| protected void updateStashedHandleHintScale() { |
| mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value); |
| mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value); |
| } |
| |
| /** |
| * Should be called when the home button is disabled, so we can hide this handle as well. |
| */ |
| public void setIsHomeButtonDisabled(boolean homeDisabled) { |
| mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_HOME_DISABLED).setValue( |
| homeDisabled ? 0 : 1); |
| } |
| |
| public boolean isStashedHandleVisible() { |
| return mStashedHandleView.getVisibility() == View.VISIBLE; |
| } |
| } |