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 static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; 19 20 import android.animation.Animator; 21 import android.animation.AnimatorListenerAdapter; 22 import android.animation.ValueAnimator; 23 import android.content.SharedPreferences; 24 import android.content.res.Resources; 25 import android.graphics.Outline; 26 import android.graphics.Rect; 27 import android.view.View; 28 import android.view.ViewOutlineProvider; 29 30 import com.android.launcher3.DeviceProfile; 31 import com.android.launcher3.LauncherPrefs; 32 import com.android.launcher3.R; 33 import com.android.launcher3.anim.AnimatedFloat; 34 import com.android.launcher3.anim.RevealOutlineAnimation; 35 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 36 import com.android.launcher3.util.DisplayController; 37 import com.android.launcher3.util.Executors; 38 import com.android.launcher3.util.MultiPropertyFactory; 39 import com.android.launcher3.util.MultiValueAlpha; 40 import com.android.systemui.shared.navigationbar.RegionSamplingHelper; 41 42 import java.io.PrintWriter; 43 44 /** 45 * Handles properties/data collection, then passes the results to our stashed handle View to render. 46 */ 47 public class StashedHandleViewController implements TaskbarControllers.LoggableTaskbarController { 48 49 public static final int ALPHA_INDEX_STASHED = 0; 50 public static final int ALPHA_INDEX_HOME_DISABLED = 1; 51 public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 2; 52 public static final int ALPHA_INDEX_HIDDEN_WHILE_DREAMING = 3; 53 private static final int NUM_ALPHA_CHANNELS = 4; 54 55 /** 56 * The SharedPreferences key for whether the stashed handle region is dark. 57 */ 58 private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY = 59 "stashed_handle_region_is_dark"; 60 61 private final TaskbarActivityContext mActivity; 62 private final SharedPreferences mPrefs; 63 private final StashedHandleView mStashedHandleView; 64 private int mStashedHandleWidth; 65 private final int mStashedHandleHeight; 66 private RegionSamplingHelper mRegionSamplingHelper; 67 private final MultiValueAlpha mTaskbarStashedHandleAlpha; 68 private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat( 69 this::updateStashedHandleHintScale); 70 71 // Initialized in init. 72 private TaskbarControllers mControllers; 73 private int mTaskbarSize; 74 75 // The bounds we want to clip to in the settled state when showing the stashed handle. 76 private final Rect mStashedHandleBounds = new Rect(); 77 private float mStashedHandleRadius; 78 79 // When the reveal animation is cancelled, we can assume it's about to create a new animation, 80 // which should start off at the same point the cancelled one left off. 81 private float mStartProgressForNextRevealAnim; 82 private boolean mWasLastRevealAnimReversed; 83 84 // States that affect whether region sampling is enabled or not 85 private boolean mIsStashed; 86 private boolean mTaskbarHidden; 87 88 private float mTranslationYForSwipe; 89 private float mTranslationYForStash; 90 StashedHandleViewController(TaskbarActivityContext activity, StashedHandleView stashedHandleView)91 public StashedHandleViewController(TaskbarActivityContext activity, 92 StashedHandleView stashedHandleView) { 93 mActivity = activity; 94 mPrefs = LauncherPrefs.getPrefs(mActivity); 95 mStashedHandleView = stashedHandleView; 96 mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, NUM_ALPHA_CHANNELS); 97 mTaskbarStashedHandleAlpha.setUpdateVisibility(true); 98 mStashedHandleView.updateHandleColor( 99 mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false), 100 false /* animate */); 101 final Resources resources = mActivity.getResources(); 102 mStashedHandleHeight = resources.getDimensionPixelSize( 103 R.dimen.taskbar_stashed_handle_height); 104 } 105 init(TaskbarControllers controllers)106 public void init(TaskbarControllers controllers) { 107 mControllers = controllers; 108 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 109 Resources resources = mActivity.getResources(); 110 if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) { 111 mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_size); 112 mStashedHandleWidth = 113 resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen); 114 } else { 115 mTaskbarSize = deviceProfile.taskbarHeight; 116 mStashedHandleWidth = resources 117 .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width); 118 } 119 int taskbarBottomMargin = deviceProfile.taskbarBottomMargin; 120 mStashedHandleView.getLayoutParams().height = mTaskbarSize + taskbarBottomMargin; 121 122 mTaskbarStashedHandleAlpha.get(ALPHA_INDEX_STASHED).setValue( 123 isPhoneGestureNavMode(deviceProfile) ? 1 : 0); 124 mTaskbarStashedHandleHintScale.updateValue(1f); 125 126 final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight(); 127 mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() { 128 @Override 129 public void getOutline(View view, Outline outline) { 130 final int stashedCenterX = view.getWidth() / 2; 131 final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2; 132 mStashedHandleBounds.set( 133 stashedCenterX - mStashedHandleWidth / 2, 134 stashedCenterY - mStashedHandleHeight / 2, 135 stashedCenterX + mStashedHandleWidth / 2, 136 stashedCenterY + mStashedHandleHeight / 2); 137 mStashedHandleView.updateSampledRegion(mStashedHandleBounds); 138 mStashedHandleRadius = view.getHeight() / 2f; 139 outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius); 140 } 141 }); 142 143 mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> { 144 final int stashedCenterX = view.getWidth() / 2; 145 final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2; 146 147 view.setPivotX(stashedCenterX); 148 view.setPivotY(stashedCenterY); 149 }); 150 initRegionSampler(); 151 if (isPhoneGestureNavMode(deviceProfile)) { 152 onIsStashedChanged(true); 153 } 154 } 155 156 /** 157 * Returns the stashed handle bounds. 158 * @param out The destination rect. 159 */ getStashedHandleBounds(Rect out)160 public void getStashedHandleBounds(Rect out) { 161 out.set(mStashedHandleBounds); 162 } 163 initRegionSampler()164 private void initRegionSampler() { 165 mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView, 166 new RegionSamplingHelper.SamplingCallback() { 167 @Override 168 public void onRegionDarknessChanged(boolean isRegionDark) { 169 mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */); 170 mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, 171 isRegionDark).apply(); 172 } 173 174 @Override 175 public Rect getSampledRegion(View sampledView) { 176 return mStashedHandleView.getSampledRegion(); 177 } 178 }, Executors.UI_HELPER_EXECUTOR); 179 } 180 181 onDestroy()182 public void onDestroy() { 183 mRegionSamplingHelper.stopAndDestroy(); 184 mRegionSamplingHelper = null; 185 } 186 isPhoneGestureNavMode(DeviceProfile deviceProfile)187 private boolean isPhoneGestureNavMode(DeviceProfile deviceProfile) { 188 return TaskbarManager.isPhoneMode(deviceProfile) && !mActivity.isThreeButtonNav(); 189 } 190 getStashedHandleAlpha()191 public MultiPropertyFactory<View> getStashedHandleAlpha() { 192 return mTaskbarStashedHandleAlpha; 193 } 194 getStashedHandleHintScale()195 public AnimatedFloat getStashedHandleHintScale() { 196 return mTaskbarStashedHandleHintScale; 197 } 198 199 /** 200 * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle 201 * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape 202 * morphs into the size of where the taskbar icons will be. 203 */ createRevealAnimToIsStashed(boolean isStashed)204 public Animator createRevealAnimToIsStashed(boolean isStashed) { 205 Rect visualBounds = new Rect(mControllers.taskbarViewController.getIconLayoutBounds()); 206 float startRadius = mStashedHandleRadius; 207 208 if (DisplayController.isTransientTaskbar(mActivity)) { 209 // Account for the full visual height of the transient taskbar. 210 int heightDiff = (mTaskbarSize - visualBounds.height()) / 2; 211 visualBounds.top -= heightDiff; 212 visualBounds.bottom += heightDiff; 213 214 startRadius = visualBounds.height() / 2f; 215 } 216 217 final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider( 218 startRadius, mStashedHandleRadius, visualBounds, mStashedHandleBounds); 219 220 boolean isReversed = !isStashed; 221 boolean changingDirection = mWasLastRevealAnimReversed != isReversed; 222 mWasLastRevealAnimReversed = isReversed; 223 if (changingDirection) { 224 mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim; 225 } 226 227 ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView, 228 isReversed, mStartProgressForNextRevealAnim); 229 revealAnim.addListener(new AnimatorListenerAdapter() { 230 @Override 231 public void onAnimationEnd(Animator animation) { 232 mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction(); 233 } 234 }); 235 return revealAnim; 236 } 237 238 /** Called when taskbar is stashed or unstashed. */ onIsStashedChanged(boolean isStashed)239 public void onIsStashedChanged(boolean isStashed) { 240 mIsStashed = isStashed; 241 updateRegionSamplingWindowVisibility(); 242 if (isStashed) { 243 mStashedHandleView.updateSampledRegion(mStashedHandleBounds); 244 mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion()); 245 } else { 246 mRegionSamplingHelper.stop(); 247 } 248 } 249 updateStashedHandleHintScale()250 protected void updateStashedHandleHintScale() { 251 mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value); 252 mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value); 253 } 254 255 /** 256 * Sets the translation of the stashed handle during the swipe up gesture. 257 */ setTranslationYForSwipe(float transY)258 protected void setTranslationYForSwipe(float transY) { 259 mTranslationYForSwipe = transY; 260 updateTranslationY(); 261 } 262 263 /** 264 * Sets the translation of the stashed handle during the spring on stash animation. 265 */ setTranslationYForStash(float transY)266 protected void setTranslationYForStash(float transY) { 267 mTranslationYForStash = transY; 268 updateTranslationY(); 269 } 270 updateTranslationY()271 private void updateTranslationY() { 272 mStashedHandleView.setTranslationY(mTranslationYForSwipe + mTranslationYForStash); 273 } 274 275 /** 276 * Should be called when the home button is disabled, so we can hide this handle as well. 277 */ setIsHomeButtonDisabled(boolean homeDisabled)278 public void setIsHomeButtonDisabled(boolean homeDisabled) { 279 mTaskbarStashedHandleAlpha.get(ALPHA_INDEX_HOME_DISABLED).setValue( 280 homeDisabled ? 0 : 1); 281 } 282 updateStateForSysuiFlags(int systemUiStateFlags)283 public void updateStateForSysuiFlags(int systemUiStateFlags) { 284 mTaskbarHidden = (systemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0; 285 updateRegionSamplingWindowVisibility(); 286 } 287 updateRegionSamplingWindowVisibility()288 private void updateRegionSamplingWindowVisibility() { 289 mRegionSamplingHelper.setWindowVisible(mIsStashed && !mTaskbarHidden); 290 } 291 isStashedHandleVisible()292 public boolean isStashedHandleVisible() { 293 return mStashedHandleView.getVisibility() == View.VISIBLE; 294 } 295 296 @Override dumpLogs(String prefix, PrintWriter pw)297 public void dumpLogs(String prefix, PrintWriter pw) { 298 pw.println(prefix + "StashedHandleViewController:"); 299 300 pw.println(prefix + "\tisStashedHandleVisible=" + isStashedHandleVisible()); 301 pw.println(prefix + "\tmStashedHandleWidth=" + mStashedHandleWidth); 302 pw.println(prefix + "\tmStashedHandleHeight=" + mStashedHandleHeight); 303 mRegionSamplingHelper.dump(prefix, pw); 304 } 305 } 306