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