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