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