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