1 /* 2 * Copyright (C) 2016 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 17 package com.android.systemui.statusbar.phone; 18 19 import android.animation.ValueAnimator; 20 import android.content.Context; 21 import android.os.Bundle; 22 import android.os.Handler; 23 import android.os.SystemClock; 24 import android.util.MathUtils; 25 import android.util.TimeUtils; 26 27 import androidx.annotation.VisibleForTesting; 28 29 import com.android.app.animation.Interpolators; 30 import com.android.internal.policy.GestureNavigationSettingsObserver; 31 import com.android.systemui.Dumpable; 32 import com.android.systemui.dagger.qualifiers.Background; 33 import com.android.systemui.plugins.statusbar.StatusBarStateController; 34 import com.android.systemui.shared.system.QuickStepContract; 35 import com.android.systemui.statusbar.CommandQueue; 36 import com.android.systemui.statusbar.CommandQueue.Callbacks; 37 import com.android.systemui.statusbar.policy.KeyguardStateController; 38 39 import dagger.assisted.Assisted; 40 import dagger.assisted.AssistedFactory; 41 import dagger.assisted.AssistedInject; 42 43 import java.io.PrintWriter; 44 import java.lang.ref.WeakReference; 45 46 /** 47 * Class to control all aspects about light bar changes. 48 */ 49 public class LightBarTransitionsController implements Dumpable { 50 51 public static final int DEFAULT_TINT_ANIMATION_DURATION = 120; 52 private static final String EXTRA_DARK_INTENSITY = "dark_intensity"; 53 54 private static class Callback implements Callbacks, StatusBarStateController.StateListener { 55 private final WeakReference<LightBarTransitionsController> mSelf; 56 Callback(LightBarTransitionsController self)57 Callback(LightBarTransitionsController self) { 58 mSelf = new WeakReference<>(self); 59 } 60 61 @Override appTransitionPending(int displayId, boolean forced)62 public void appTransitionPending(int displayId, boolean forced) { 63 LightBarTransitionsController self = mSelf.get(); 64 if (self != null) { 65 self.appTransitionPending(displayId, forced); 66 } 67 } 68 69 @Override appTransitionCancelled(int displayId)70 public void appTransitionCancelled(int displayId) { 71 LightBarTransitionsController self = mSelf.get(); 72 if (self != null) { 73 self.appTransitionCancelled(displayId); 74 } 75 } 76 77 @Override appTransitionStarting(int displayId, long startTime, long duration, boolean forced)78 public void appTransitionStarting(int displayId, long startTime, long duration, 79 boolean forced) { 80 LightBarTransitionsController self = mSelf.get(); 81 if (self != null) { 82 self.appTransitionStarting(displayId, startTime, duration, forced); 83 } 84 } 85 86 @Override onDozeAmountChanged(float linear, float eased)87 public void onDozeAmountChanged(float linear, float eased) { 88 LightBarTransitionsController self = mSelf.get(); 89 if (self != null) { 90 self.onDozeAmountChanged(linear, eased); 91 } 92 } 93 } 94 95 private final Callback mCallback; 96 97 private final Handler mHandler; 98 private final DarkIntensityApplier mApplier; 99 private final KeyguardStateController mKeyguardStateController; 100 private final StatusBarStateController mStatusBarStateController; 101 private final CommandQueue mCommandQueue; 102 private GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; 103 104 private boolean mTransitionDeferring; 105 private long mTransitionDeferringStartTime; 106 private long mTransitionDeferringDuration; 107 private boolean mTransitionPending; 108 private boolean mTintChangePending; 109 private boolean mNavigationButtonsForcedVisible; 110 private float mPendingDarkIntensity; 111 private ValueAnimator mTintAnimator; 112 private float mDarkIntensity; 113 private float mNextDarkIntensity; 114 private float mDozeAmount; 115 private int mDisplayId; 116 private final Runnable mTransitionDeferringDoneRunnable = new Runnable() { 117 @Override 118 public void run() { 119 mTransitionDeferring = false; 120 } 121 }; 122 123 private final Context mContext; 124 125 @AssistedInject LightBarTransitionsController( Context context, @Background Handler bgHandler, @Assisted DarkIntensityApplier applier, CommandQueue commandQueue, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController)126 public LightBarTransitionsController( 127 Context context, 128 @Background Handler bgHandler, 129 @Assisted DarkIntensityApplier applier, 130 CommandQueue commandQueue, 131 KeyguardStateController keyguardStateController, 132 StatusBarStateController statusBarStateController) { 133 mApplier = applier; 134 mHandler = new Handler(); 135 mKeyguardStateController = keyguardStateController; 136 mStatusBarStateController = statusBarStateController; 137 mCommandQueue = commandQueue; 138 mCallback = new Callback(this); 139 mCommandQueue.addCallback(mCallback); 140 mStatusBarStateController.addCallback(mCallback); 141 mDozeAmount = mStatusBarStateController.getDozeAmount(); 142 mContext = context; 143 mDisplayId = mContext.getDisplayId(); 144 mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( 145 mHandler, bgHandler, mContext, this::onNavigationSettingsChanged); 146 mGestureNavigationSettingsObserver.register(); 147 onNavigationSettingsChanged(); 148 } 149 150 /** Call to cleanup the LightBarTransitionsController when done with it. */ destroy()151 public void destroy() { 152 mCommandQueue.removeCallback(mCallback); 153 mStatusBarStateController.removeCallback(mCallback); 154 mGestureNavigationSettingsObserver.unregister(); 155 } 156 saveState(Bundle outState)157 public void saveState(Bundle outState) { 158 float intensity = mTintAnimator != null && mTintAnimator.isRunning() 159 ? mNextDarkIntensity : mDarkIntensity; 160 outState.putFloat(EXTRA_DARK_INTENSITY, intensity); 161 } 162 restoreState(Bundle savedInstanceState)163 public void restoreState(Bundle savedInstanceState) { 164 setIconTintInternal(savedInstanceState.getFloat(EXTRA_DARK_INTENSITY, 0)); 165 mNextDarkIntensity = mDarkIntensity; 166 } 167 appTransitionPending(int displayId, boolean forced)168 private void appTransitionPending(int displayId, boolean forced) { 169 if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) { 170 return; 171 } 172 mTransitionPending = true; 173 } 174 appTransitionCancelled(int displayId)175 private void appTransitionCancelled(int displayId) { 176 if (mDisplayId != displayId) { 177 return; 178 } 179 if (mTransitionPending && mTintChangePending) { 180 mTintChangePending = false; 181 animateIconTint(mPendingDarkIntensity, 0 /* delay */, 182 mApplier.getTintAnimationDuration()); 183 } 184 mTransitionPending = false; 185 } 186 appTransitionStarting(int displayId, long startTime, long duration, boolean forced)187 private void appTransitionStarting(int displayId, long startTime, long duration, boolean forced) { 188 if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) { 189 return; 190 } 191 if (mTransitionPending && mTintChangePending) { 192 mTintChangePending = false; 193 animateIconTint(mPendingDarkIntensity, 194 Math.max(0, startTime - SystemClock.uptimeMillis()), 195 duration); 196 197 } else if (mTransitionPending) { 198 199 // If we don't have a pending tint change yet, the change might come in the future until 200 // startTime is reached. 201 mTransitionDeferring = true; 202 mTransitionDeferringStartTime = startTime; 203 mTransitionDeferringDuration = duration; 204 mHandler.removeCallbacks(mTransitionDeferringDoneRunnable); 205 mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime); 206 } 207 mTransitionPending = false; 208 } 209 210 @VisibleForTesting setNavigationSettingsObserver(GestureNavigationSettingsObserver observer)211 void setNavigationSettingsObserver(GestureNavigationSettingsObserver observer) { 212 mGestureNavigationSettingsObserver = observer; 213 onNavigationSettingsChanged(); 214 } 215 setIconsDark(boolean dark, boolean animate)216 public void setIconsDark(boolean dark, boolean animate) { 217 if (!animate) { 218 setIconTintInternal(dark ? 1.0f : 0.0f); 219 mNextDarkIntensity = dark ? 1.0f : 0.0f; 220 } else if (mTransitionPending) { 221 deferIconTintChange(dark ? 1.0f : 0.0f); 222 } else if (mTransitionDeferring) { 223 animateIconTint(dark ? 1.0f : 0.0f, 224 Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()), 225 mTransitionDeferringDuration); 226 } else { 227 animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, mApplier.getTintAnimationDuration()); 228 } 229 } 230 getCurrentDarkIntensity()231 public float getCurrentDarkIntensity() { 232 return mDarkIntensity; 233 } 234 deferIconTintChange(float darkIntensity)235 private void deferIconTintChange(float darkIntensity) { 236 if (mTintChangePending && darkIntensity == mPendingDarkIntensity) { 237 return; 238 } 239 mTintChangePending = true; 240 mPendingDarkIntensity = darkIntensity; 241 } 242 animateIconTint(float targetDarkIntensity, long delay, long duration)243 private void animateIconTint(float targetDarkIntensity, long delay, 244 long duration) { 245 if (mNextDarkIntensity == targetDarkIntensity) { 246 return; 247 } 248 if (mTintAnimator != null) { 249 mTintAnimator.cancel(); 250 } 251 mNextDarkIntensity = targetDarkIntensity; 252 mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity); 253 mTintAnimator.addUpdateListener( 254 animation -> setIconTintInternal((Float) animation.getAnimatedValue())); 255 mTintAnimator.setDuration(duration); 256 mTintAnimator.setStartDelay(delay); 257 mTintAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 258 mTintAnimator.start(); 259 } 260 setIconTintInternal(float darkIntensity)261 private void setIconTintInternal(float darkIntensity) { 262 mDarkIntensity = darkIntensity; 263 dispatchDark(); 264 } 265 dispatchDark()266 private void dispatchDark() { 267 mApplier.applyDarkIntensity(MathUtils.lerp(mDarkIntensity, 0f, mDozeAmount)); 268 } 269 onDozeAmountChanged(float linear, float eased)270 public void onDozeAmountChanged(float linear, float eased) { 271 mDozeAmount = eased; 272 dispatchDark(); 273 } 274 275 /** 276 * Called when the navigation settings change. 277 */ onNavigationSettingsChanged()278 private void onNavigationSettingsChanged() { 279 mNavigationButtonsForcedVisible = 280 mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); 281 } 282 283 /** 284 * Return whether to use the tint calculated in this class for nav icons. 285 */ supportsIconTintForNavMode(int navigationMode)286 public boolean supportsIconTintForNavMode(int navigationMode) { 287 // In gesture mode, we already do region sampling to update tint based on content beneath. 288 return !QuickStepContract.isGesturalMode(navigationMode) 289 || mNavigationButtonsForcedVisible; 290 } 291 292 @Override dump(PrintWriter pw, String[] args)293 public void dump(PrintWriter pw, String[] args) { 294 pw.print(" mTransitionDeferring="); pw.print(mTransitionDeferring); 295 if (mTransitionDeferring) { 296 pw.println(); 297 pw.print(" mTransitionDeferringStartTime="); 298 pw.println(TimeUtils.formatUptime(mTransitionDeferringStartTime)); 299 300 pw.print(" mTransitionDeferringDuration="); 301 TimeUtils.formatDuration(mTransitionDeferringDuration, pw); 302 pw.println(); 303 } 304 pw.print(" mTransitionPending="); pw.print(mTransitionPending); 305 pw.print(" mTintChangePending="); pw.println(mTintChangePending); 306 307 pw.print(" mPendingDarkIntensity="); pw.print(mPendingDarkIntensity); 308 pw.print(" mDarkIntensity="); pw.print(mDarkIntensity); 309 pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity); 310 pw.print(" mAreNavigationButtonForcedVisible="); pw.println(mNavigationButtonsForcedVisible); 311 } 312 313 /** 314 * Interface to apply a specific dark intensity. 315 */ 316 public interface DarkIntensityApplier { applyDarkIntensity(float darkIntensity)317 void applyDarkIntensity(float darkIntensity); getTintAnimationDuration()318 int getTintAnimationDuration(); 319 } 320 321 /** Injectable factory for construction a LightBarTransitionsController. */ 322 @AssistedFactory 323 public interface Factory { 324 /** */ create(DarkIntensityApplier darkIntensityApplier)325 LightBarTransitionsController create(DarkIntensityApplier darkIntensityApplier); 326 } 327 } 328