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