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.app.animation.Interpolators;
28 import com.android.systemui.Dumpable;
29 import com.android.systemui.plugins.statusbar.StatusBarStateController;
30 import com.android.systemui.shared.system.QuickStepContract;
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 dagger.assisted.Assisted;
36 import dagger.assisted.AssistedFactory;
37 import dagger.assisted.AssistedInject;
38 
39 import java.io.PrintWriter;
40 import java.lang.ref.WeakReference;
41 
42 /**
43  * Class to control all aspects about light bar changes.
44  */
45 public class LightBarTransitionsController implements Dumpable {
46 
47     public static final int DEFAULT_TINT_ANIMATION_DURATION = 120;
48     private static final String EXTRA_DARK_INTENSITY = "dark_intensity";
49 
50     private static class Callback implements Callbacks, StatusBarStateController.StateListener {
51         private final WeakReference<LightBarTransitionsController> mSelf;
52 
Callback(LightBarTransitionsController self)53         Callback(LightBarTransitionsController self) {
54             mSelf = new WeakReference<>(self);
55         }
56 
57         @Override
appTransitionPending(int displayId, boolean forced)58         public void appTransitionPending(int displayId, boolean forced) {
59             LightBarTransitionsController self = mSelf.get();
60             if (self != null) {
61                 self.appTransitionPending(displayId, forced);
62             }
63         }
64 
65         @Override
appTransitionCancelled(int displayId)66         public void appTransitionCancelled(int displayId) {
67             LightBarTransitionsController self = mSelf.get();
68             if (self != null) {
69                 self.appTransitionCancelled(displayId);
70             }
71         }
72 
73         @Override
appTransitionStarting(int displayId, long startTime, long duration, boolean forced)74         public void appTransitionStarting(int displayId, long startTime, long duration,
75                 boolean forced) {
76             LightBarTransitionsController self = mSelf.get();
77             if (self != null) {
78                 self.appTransitionStarting(displayId, startTime, duration, forced);
79             }
80         }
81 
82         @Override
onDozeAmountChanged(float linear, float eased)83         public void onDozeAmountChanged(float linear, float eased) {
84             LightBarTransitionsController self = mSelf.get();
85             if (self != null) {
86                 self.onDozeAmountChanged(linear, eased);
87             }
88         }
89     }
90 
91     private final Callback mCallback;
92 
93     private final Handler mHandler;
94     private final DarkIntensityApplier mApplier;
95     private final KeyguardStateController mKeyguardStateController;
96     private final StatusBarStateController mStatusBarStateController;
97     private final CommandQueue mCommandQueue;
98 
99     private boolean mTransitionDeferring;
100     private long mTransitionDeferringStartTime;
101     private long mTransitionDeferringDuration;
102     private boolean mTransitionPending;
103     private boolean mTintChangePending;
104     private float mPendingDarkIntensity;
105     private ValueAnimator mTintAnimator;
106     private float mDarkIntensity;
107     private float mNextDarkIntensity;
108     private float mDozeAmount;
109     private int mDisplayId;
110     private final Runnable mTransitionDeferringDoneRunnable = new Runnable() {
111         @Override
112         public void run() {
113             mTransitionDeferring = false;
114         }
115     };
116 
117     private final Context mContext;
118 
119     @AssistedInject
LightBarTransitionsController( Context context, @Assisted DarkIntensityApplier applier, CommandQueue commandQueue, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController)120     public LightBarTransitionsController(
121             Context context,
122             @Assisted DarkIntensityApplier applier,
123             CommandQueue commandQueue,
124             KeyguardStateController keyguardStateController,
125             StatusBarStateController statusBarStateController) {
126         mApplier = applier;
127         mHandler = new Handler();
128         mKeyguardStateController = keyguardStateController;
129         mStatusBarStateController = statusBarStateController;
130         mCommandQueue = commandQueue;
131         mCallback = new Callback(this);
132         mCommandQueue.addCallback(mCallback);
133         mStatusBarStateController.addCallback(mCallback);
134         mDozeAmount = mStatusBarStateController.getDozeAmount();
135         mContext = context;
136         mDisplayId = mContext.getDisplayId();
137     }
138 
139     /** Call to cleanup the LightBarTransitionsController when done with it. */
destroy()140     public void destroy() {
141         mCommandQueue.removeCallback(mCallback);
142         mStatusBarStateController.removeCallback(mCallback);
143     }
144 
saveState(Bundle outState)145     public void saveState(Bundle outState) {
146         float intensity = mTintAnimator != null && mTintAnimator.isRunning()
147                 ?  mNextDarkIntensity : mDarkIntensity;
148         outState.putFloat(EXTRA_DARK_INTENSITY, intensity);
149     }
150 
restoreState(Bundle savedInstanceState)151     public void restoreState(Bundle savedInstanceState) {
152         setIconTintInternal(savedInstanceState.getFloat(EXTRA_DARK_INTENSITY, 0));
153         mNextDarkIntensity = mDarkIntensity;
154     }
155 
appTransitionPending(int displayId, boolean forced)156     private void appTransitionPending(int displayId, boolean forced) {
157         if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
158             return;
159         }
160         mTransitionPending = true;
161     }
162 
appTransitionCancelled(int displayId)163     private void appTransitionCancelled(int displayId) {
164         if (mDisplayId != displayId) {
165             return;
166         }
167         if (mTransitionPending && mTintChangePending) {
168             mTintChangePending = false;
169             animateIconTint(mPendingDarkIntensity, 0 /* delay */,
170                     mApplier.getTintAnimationDuration());
171         }
172         mTransitionPending = false;
173     }
174 
appTransitionStarting(int displayId, long startTime, long duration, boolean forced)175     private void appTransitionStarting(int displayId, long startTime, long duration, boolean forced) {
176         if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
177             return;
178         }
179         if (mTransitionPending && mTintChangePending) {
180             mTintChangePending = false;
181             animateIconTint(mPendingDarkIntensity,
182                     Math.max(0, startTime - SystemClock.uptimeMillis()),
183                     duration);
184 
185         } else if (mTransitionPending) {
186 
187             // If we don't have a pending tint change yet, the change might come in the future until
188             // startTime is reached.
189             mTransitionDeferring = true;
190             mTransitionDeferringStartTime = startTime;
191             mTransitionDeferringDuration = duration;
192             mHandler.removeCallbacks(mTransitionDeferringDoneRunnable);
193             mHandler.postAtTime(mTransitionDeferringDoneRunnable, startTime);
194         }
195         mTransitionPending = false;
196     }
197 
setIconsDark(boolean dark, boolean animate)198     public void setIconsDark(boolean dark, boolean animate) {
199         if (!animate) {
200             setIconTintInternal(dark ? 1.0f : 0.0f);
201             mNextDarkIntensity = dark ? 1.0f : 0.0f;
202         } else if (mTransitionPending) {
203             deferIconTintChange(dark ? 1.0f : 0.0f);
204         } else if (mTransitionDeferring) {
205             animateIconTint(dark ? 1.0f : 0.0f,
206                     Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()),
207                     mTransitionDeferringDuration);
208         } else {
209             animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, mApplier.getTintAnimationDuration());
210         }
211     }
212 
getCurrentDarkIntensity()213     public float getCurrentDarkIntensity() {
214         return mDarkIntensity;
215     }
216 
deferIconTintChange(float darkIntensity)217     private void deferIconTintChange(float darkIntensity) {
218         if (mTintChangePending && darkIntensity == mPendingDarkIntensity) {
219             return;
220         }
221         mTintChangePending = true;
222         mPendingDarkIntensity = darkIntensity;
223     }
224 
animateIconTint(float targetDarkIntensity, long delay, long duration)225     private void animateIconTint(float targetDarkIntensity, long delay,
226             long duration) {
227         if (mNextDarkIntensity == targetDarkIntensity) {
228             return;
229         }
230         if (mTintAnimator != null) {
231             mTintAnimator.cancel();
232         }
233         mNextDarkIntensity = targetDarkIntensity;
234         mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
235         mTintAnimator.addUpdateListener(
236                 animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
237         mTintAnimator.setDuration(duration);
238         mTintAnimator.setStartDelay(delay);
239         mTintAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
240         mTintAnimator.start();
241     }
242 
setIconTintInternal(float darkIntensity)243     private void setIconTintInternal(float darkIntensity) {
244         mDarkIntensity = darkIntensity;
245         dispatchDark();
246     }
247 
dispatchDark()248     private void dispatchDark() {
249         mApplier.applyDarkIntensity(MathUtils.lerp(mDarkIntensity, 0f, mDozeAmount));
250     }
251 
252     @Override
dump(PrintWriter pw, String[] args)253     public void dump(PrintWriter pw, String[] args) {
254         pw.print("  mTransitionDeferring="); pw.print(mTransitionDeferring);
255         if (mTransitionDeferring) {
256             pw.println();
257             pw.print("   mTransitionDeferringStartTime=");
258             pw.println(TimeUtils.formatUptime(mTransitionDeferringStartTime));
259 
260             pw.print("   mTransitionDeferringDuration=");
261             TimeUtils.formatDuration(mTransitionDeferringDuration, pw);
262             pw.println();
263         }
264         pw.print("  mTransitionPending="); pw.print(mTransitionPending);
265         pw.print(" mTintChangePending="); pw.println(mTintChangePending);
266 
267         pw.print("  mPendingDarkIntensity="); pw.print(mPendingDarkIntensity);
268         pw.print(" mDarkIntensity="); pw.print(mDarkIntensity);
269         pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity);
270     }
271 
onDozeAmountChanged(float linear, float eased)272     public void onDozeAmountChanged(float linear, float eased) {
273         mDozeAmount = eased;
274         dispatchDark();
275     }
276 
277     /**
278      * Return whether to use the tint calculated in this class for nav icons.
279      */
supportsIconTintForNavMode(int navigationMode)280     public boolean supportsIconTintForNavMode(int navigationMode) {
281         // In gesture mode, we already do region sampling to update tint based on content beneath.
282         return !QuickStepContract.isGesturalMode(navigationMode);
283     }
284 
285     /**
286      * Interface to apply a specific dark intensity.
287      */
288     public interface DarkIntensityApplier {
applyDarkIntensity(float darkIntensity)289         void applyDarkIntensity(float darkIntensity);
getTintAnimationDuration()290         int getTintAnimationDuration();
291     }
292 
293     /** Injectable factory for construction a LightBarTransitionsController. */
294     @AssistedFactory
295     public interface Factory {
296         /** */
create(DarkIntensityApplier darkIntensityApplier)297         LightBarTransitionsController create(DarkIntensityApplier darkIntensityApplier);
298     }
299 }
300