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.Interpolators; 30 import com.android.systemui.SysUiServiceProvider; 31 import com.android.systemui.plugins.statusbar.StatusBarStateController; 32 import com.android.systemui.statusbar.CommandQueue; 33 import com.android.systemui.statusbar.CommandQueue.Callbacks; 34 import com.android.systemui.statusbar.policy.KeyguardMonitor; 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 KeyguardMonitor mKeyguardMonitor; 51 private final StatusBarStateController mStatusBarStateController; 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)73 public LightBarTransitionsController(Context context, DarkIntensityApplier applier) { 74 mApplier = applier; 75 mHandler = new Handler(); 76 mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); 77 mStatusBarStateController = Dependency.get(StatusBarStateController.class); 78 SysUiServiceProvider.getComponent(context, CommandQueue.class) 79 .addCallback(this); 80 mStatusBarStateController.addCallback(this); 81 mDozeAmount = mStatusBarStateController.getDozeAmount(); 82 mContext = context; 83 mDisplayId = mContext.getDisplayId(); 84 } 85 destroy(Context context)86 public void destroy(Context context) { 87 SysUiServiceProvider.getComponent(context, CommandQueue.class) 88 .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 } 101 102 @Override appTransitionPending(int displayId, boolean forced)103 public void appTransitionPending(int displayId, boolean forced) { 104 if (mDisplayId != displayId || mKeyguardMonitor.isKeyguardGoingAway() && !forced) { 105 return; 106 } 107 mTransitionPending = true; 108 } 109 110 @Override appTransitionCancelled(int displayId)111 public void appTransitionCancelled(int displayId) { 112 if (mDisplayId != displayId) { 113 return; 114 } 115 if (mTransitionPending && mTintChangePending) { 116 mTintChangePending = false; 117 animateIconTint(mPendingDarkIntensity, 0 /* delay */, 118 mApplier.getTintAnimationDuration()); 119 } 120 mTransitionPending = false; 121 } 122 123 @Override appTransitionStarting(int displayId, long startTime, long duration, boolean forced)124 public void appTransitionStarting(int displayId, long startTime, long duration, 125 boolean forced) { 126 if (mDisplayId != displayId || mKeyguardMonitor.isKeyguardGoingAway() && !forced) { 127 return; 128 } 129 if (mTransitionPending && mTintChangePending) { 130 mTintChangePending = false; 131 animateIconTint(mPendingDarkIntensity, 132 Math.max(0, startTime - SystemClock.uptimeMillis()), 133 duration); 134 135 } else if (mTransitionPending) { 136 137 // If we don't have a pending tint change yet, the change might come in the future until 138 // startTime is reached. 139 mTransitionDeferring = true; 140 mTransitionDeferringStartTime = startTime; 141 mTransitionDeferringDuration = duration; 142 mHandler.removeCallbacks(mTransitionDeferringDoneRunnable); 143 mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime); 144 } 145 mTransitionPending = false; 146 } 147 setIconsDark(boolean dark, boolean animate)148 public void setIconsDark(boolean dark, boolean animate) { 149 if (!animate) { 150 setIconTintInternal(dark ? 1.0f : 0.0f); 151 mNextDarkIntensity = dark ? 1.0f : 0.0f; 152 } else if (mTransitionPending) { 153 deferIconTintChange(dark ? 1.0f : 0.0f); 154 } else if (mTransitionDeferring) { 155 animateIconTint(dark ? 1.0f : 0.0f, 156 Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()), 157 mTransitionDeferringDuration); 158 } else { 159 animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, mApplier.getTintAnimationDuration()); 160 } 161 } 162 getCurrentDarkIntensity()163 public float getCurrentDarkIntensity() { 164 return mDarkIntensity; 165 } 166 deferIconTintChange(float darkIntensity)167 private void deferIconTintChange(float darkIntensity) { 168 if (mTintChangePending && darkIntensity == mPendingDarkIntensity) { 169 return; 170 } 171 mTintChangePending = true; 172 mPendingDarkIntensity = darkIntensity; 173 } 174 animateIconTint(float targetDarkIntensity, long delay, long duration)175 private void animateIconTint(float targetDarkIntensity, long delay, 176 long duration) { 177 if (mNextDarkIntensity == targetDarkIntensity) { 178 return; 179 } 180 if (mTintAnimator != null) { 181 mTintAnimator.cancel(); 182 } 183 mNextDarkIntensity = targetDarkIntensity; 184 mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity); 185 mTintAnimator.addUpdateListener( 186 animation -> setIconTintInternal((Float) animation.getAnimatedValue())); 187 mTintAnimator.setDuration(duration); 188 mTintAnimator.setStartDelay(delay); 189 mTintAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 190 mTintAnimator.start(); 191 } 192 setIconTintInternal(float darkIntensity)193 private void setIconTintInternal(float darkIntensity) { 194 mDarkIntensity = darkIntensity; 195 dispatchDark(); 196 } 197 dispatchDark()198 private void dispatchDark() { 199 mApplier.applyDarkIntensity(MathUtils.lerp(mDarkIntensity, 0f, mDozeAmount)); 200 } 201 202 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)203 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 204 pw.print(" mTransitionDeferring="); pw.print(mTransitionDeferring); 205 if (mTransitionDeferring) { 206 pw.println(); 207 pw.print(" mTransitionDeferringStartTime="); 208 pw.println(TimeUtils.formatUptime(mTransitionDeferringStartTime)); 209 210 pw.print(" mTransitionDeferringDuration="); 211 TimeUtils.formatDuration(mTransitionDeferringDuration, pw); 212 pw.println(); 213 } 214 pw.print(" mTransitionPending="); pw.print(mTransitionPending); 215 pw.print(" mTintChangePending="); pw.println(mTintChangePending); 216 217 pw.print(" mPendingDarkIntensity="); pw.print(mPendingDarkIntensity); 218 pw.print(" mDarkIntensity="); pw.print(mDarkIntensity); 219 pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity); 220 } 221 222 @Override onStateChanged(int newState)223 public void onStateChanged(int newState) { } 224 225 @Override onDozeAmountChanged(float linear, float eased)226 public void onDozeAmountChanged(float linear, float eased) { 227 mDozeAmount = eased; 228 dispatchDark(); 229 } 230 231 /** 232 * Interface to apply a specific dark intensity. 233 */ 234 public interface DarkIntensityApplier { applyDarkIntensity(float darkIntensity)235 void applyDarkIntensity(float darkIntensity); getTintAnimationDuration()236 int getTintAnimationDuration(); 237 } 238 } 239