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