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