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.Dependency; 27 import com.android.systemui.Dumpable; 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 private final Runnable mTransitionDeferringDoneRunnable = new Runnable() { 59 @Override 60 public void run() { 61 mTransitionDeferring = false; 62 } 63 }; 64 LightBarTransitionsController(Context context, DarkIntensityApplier applier)65 public LightBarTransitionsController(Context context, DarkIntensityApplier applier) { 66 mApplier = applier; 67 mHandler = new Handler(); 68 mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); 69 SysUiServiceProvider.getComponent(context, CommandQueue.class) 70 .addCallbacks(this); 71 } 72 destroy(Context context)73 public void destroy(Context context) { 74 SysUiServiceProvider.getComponent(context, CommandQueue.class) 75 .removeCallbacks(this); 76 } 77 saveState(Bundle outState)78 public void saveState(Bundle outState) { 79 float intensity = mTintAnimator != null && mTintAnimator.isRunning() 80 ? mNextDarkIntensity : mDarkIntensity; 81 outState.putFloat(EXTRA_DARK_INTENSITY, intensity); 82 } 83 restoreState(Bundle savedInstanceState)84 public void restoreState(Bundle savedInstanceState) { 85 setIconTintInternal(savedInstanceState.getFloat(EXTRA_DARK_INTENSITY, 0)); 86 } 87 88 @Override appTransitionPending(boolean forced)89 public void appTransitionPending(boolean forced) { 90 if (mKeyguardMonitor.isKeyguardGoingAway() && !forced) { 91 return; 92 } 93 mTransitionPending = true; 94 } 95 96 @Override appTransitionCancelled()97 public void appTransitionCancelled() { 98 if (mTransitionPending && mTintChangePending) { 99 mTintChangePending = false; 100 animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); 101 } 102 mTransitionPending = false; 103 } 104 105 @Override appTransitionStarting(long startTime, long duration, boolean forced)106 public void appTransitionStarting(long startTime, long duration, boolean forced) { 107 if (mKeyguardMonitor.isKeyguardGoingAway() && !forced) { 108 return; 109 } 110 if (mTransitionPending && mTintChangePending) { 111 mTintChangePending = false; 112 animateIconTint(mPendingDarkIntensity, 113 Math.max(0, startTime - SystemClock.uptimeMillis()), 114 duration); 115 116 } else if (mTransitionPending) { 117 118 // If we don't have a pending tint change yet, the change might come in the future until 119 // startTime is reached. 120 mTransitionDeferring = true; 121 mTransitionDeferringStartTime = startTime; 122 mTransitionDeferringDuration = duration; 123 mHandler.removeCallbacks(mTransitionDeferringDoneRunnable); 124 mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime); 125 } 126 mTransitionPending = false; 127 } 128 setIconsDark(boolean dark, boolean animate)129 public void setIconsDark(boolean dark, boolean animate) { 130 if (!animate) { 131 setIconTintInternal(dark ? 1.0f : 0.0f); 132 mNextDarkIntensity = 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 (mNextDarkIntensity == targetDarkIntensity) { 159 return; 160 } 161 if (mTintAnimator != null) { 162 mTintAnimator.cancel(); 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.LINEAR_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