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