1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.taskbar; 17 18 import android.animation.Animator; 19 import android.animation.AnimatorListenerAdapter; 20 import android.animation.ValueAnimator; 21 import android.content.SharedPreferences; 22 import android.content.res.Resources; 23 import android.graphics.Outline; 24 import android.graphics.Rect; 25 import android.view.View; 26 import android.view.ViewOutlineProvider; 27 28 import com.android.launcher3.R; 29 import com.android.launcher3.Utilities; 30 import com.android.launcher3.anim.RevealOutlineAnimation; 31 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 32 import com.android.launcher3.util.Executors; 33 import com.android.launcher3.util.MultiValueAlpha; 34 import com.android.quickstep.AnimatedFloat; 35 import com.android.systemui.shared.navigationbar.RegionSamplingHelper; 36 37 /** 38 * Handles properties/data collection, then passes the results to our stashed handle View to render. 39 */ 40 public class StashedHandleViewController { 41 42 public static final int ALPHA_INDEX_STASHED = 0; 43 public static final int ALPHA_INDEX_HOME_DISABLED = 1; 44 private static final int NUM_ALPHA_CHANNELS = 2; 45 46 /** 47 * The SharedPreferences key for whether the stashed handle region is dark. 48 */ 49 private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY = 50 "stashed_handle_region_is_dark"; 51 52 private final TaskbarActivityContext mActivity; 53 private final SharedPreferences mPrefs; 54 private final StashedHandleView mStashedHandleView; 55 private final int mStashedHandleWidth; 56 private final int mStashedHandleHeight; 57 private final RegionSamplingHelper mRegionSamplingHelper; 58 private final MultiValueAlpha mTaskbarStashedHandleAlpha; 59 private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat( 60 this::updateStashedHandleHintScale); 61 62 // Initialized in init. 63 private TaskbarControllers mControllers; 64 65 // The bounds we want to clip to in the settled state when showing the stashed handle. 66 private final Rect mStashedHandleBounds = new Rect(); 67 private float mStashedHandleRadius; 68 69 // When the reveal animation is cancelled, we can assume it's about to create a new animation, 70 // which should start off at the same point the cancelled one left off. 71 private float mStartProgressForNextRevealAnim; 72 private boolean mWasLastRevealAnimReversed; 73 StashedHandleViewController(TaskbarActivityContext activity, StashedHandleView stashedHandleView)74 public StashedHandleViewController(TaskbarActivityContext activity, 75 StashedHandleView stashedHandleView) { 76 mActivity = activity; 77 mPrefs = Utilities.getPrefs(mActivity); 78 mStashedHandleView = stashedHandleView; 79 mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, NUM_ALPHA_CHANNELS); 80 mTaskbarStashedHandleAlpha.setUpdateVisibility(true); 81 mStashedHandleView.updateHandleColor( 82 mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false), 83 false /* animate */); 84 final Resources resources = mActivity.getResources(); 85 mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width); 86 mStashedHandleHeight = resources.getDimensionPixelSize( 87 R.dimen.taskbar_stashed_handle_height); 88 mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView, 89 new RegionSamplingHelper.SamplingCallback() { 90 @Override 91 public void onRegionDarknessChanged(boolean isRegionDark) { 92 mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */); 93 mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, 94 isRegionDark).apply(); 95 } 96 97 @Override 98 public Rect getSampledRegion(View sampledView) { 99 return mStashedHandleView.getSampledRegion(); 100 } 101 }, Executors.UI_HELPER_EXECUTOR); 102 } 103 init(TaskbarControllers controllers)104 public void init(TaskbarControllers controllers) { 105 mControllers = controllers; 106 mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; 107 108 mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(0); 109 mTaskbarStashedHandleHintScale.updateValue(1f); 110 111 final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight(); 112 mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() { 113 @Override 114 public void getOutline(View view, Outline outline) { 115 final int stashedCenterX = view.getWidth() / 2; 116 final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2; 117 mStashedHandleBounds.set( 118 stashedCenterX - mStashedHandleWidth / 2, 119 stashedCenterY - mStashedHandleHeight / 2, 120 stashedCenterX + mStashedHandleWidth / 2, 121 stashedCenterY + mStashedHandleHeight / 2); 122 mStashedHandleView.updateSampledRegion(mStashedHandleBounds); 123 mStashedHandleRadius = view.getHeight() / 2f; 124 outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius); 125 } 126 }); 127 128 mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> { 129 final int stashedCenterX = view.getWidth() / 2; 130 final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2; 131 132 view.setPivotX(stashedCenterX); 133 view.setPivotY(stashedCenterY); 134 }); 135 } 136 onDestroy()137 public void onDestroy() { 138 mRegionSamplingHelper.stopAndDestroy(); 139 } 140 getStashedHandleAlpha()141 public MultiValueAlpha getStashedHandleAlpha() { 142 return mTaskbarStashedHandleAlpha; 143 } 144 getStashedHandleHintScale()145 public AnimatedFloat getStashedHandleHintScale() { 146 return mTaskbarStashedHandleHintScale; 147 } 148 149 /** 150 * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle 151 * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape 152 * morphs into the size of where the taskbar icons will be. 153 */ createRevealAnimToIsStashed(boolean isStashed)154 public Animator createRevealAnimToIsStashed(boolean isStashed) { 155 final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider( 156 mStashedHandleRadius, mStashedHandleRadius, 157 mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds); 158 159 boolean isReversed = !isStashed; 160 boolean changingDirection = mWasLastRevealAnimReversed != isReversed; 161 mWasLastRevealAnimReversed = isReversed; 162 if (changingDirection) { 163 mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim; 164 } 165 166 ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView, 167 isReversed, mStartProgressForNextRevealAnim); 168 revealAnim.addListener(new AnimatorListenerAdapter() { 169 @Override 170 public void onAnimationEnd(Animator animation) { 171 mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction(); 172 } 173 }); 174 return revealAnim; 175 } 176 onIsStashed(boolean isStashed)177 public void onIsStashed(boolean isStashed) { 178 mRegionSamplingHelper.setWindowVisible(isStashed); 179 if (isStashed) { 180 mStashedHandleView.updateSampledRegion(mStashedHandleBounds); 181 mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion()); 182 } else { 183 mRegionSamplingHelper.stop(); 184 } 185 } 186 updateStashedHandleHintScale()187 protected void updateStashedHandleHintScale() { 188 mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value); 189 mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value); 190 } 191 192 /** 193 * Should be called when the home button is disabled, so we can hide this handle as well. 194 */ setIsHomeButtonDisabled(boolean homeDisabled)195 public void setIsHomeButtonDisabled(boolean homeDisabled) { 196 mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_HOME_DISABLED).setValue( 197 homeDisabled ? 0 : 1); 198 } 199 isStashedHandleVisible()200 public boolean isStashedHandleVisible() { 201 return mStashedHandleView.getVisibility() == View.VISIBLE; 202 } 203 } 204