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