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