1/*
2 * Copyright (C) 2015 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
17package com.android.server.policy;
18
19import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
20import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
21import static android.view.WindowManagerInternal.AppTransitionListener;
22
23import android.app.StatusBarManager;
24import android.os.IBinder;
25import android.os.SystemClock;
26import android.view.View;
27import android.view.animation.Animation;
28import android.view.animation.AnimationSet;
29import android.view.animation.Interpolator;
30import android.view.animation.TranslateAnimation;
31
32import com.android.server.LocalServices;
33import com.android.server.statusbar.StatusBarManagerInternal;
34
35/**
36 * Implements status bar specific behavior.
37 */
38public class StatusBarController extends BarController {
39
40    private static final long TRANSITION_DURATION = 120L;
41
42    private final AppTransitionListener mAppTransitionListener
43            = new AppTransitionListener() {
44
45        @Override
46        public void onAppTransitionPendingLocked() {
47            mHandler.post(new Runnable() {
48                @Override
49                public void run() {
50                    StatusBarManagerInternal statusbar = getStatusBarInternal();
51                    if (statusbar != null) {
52                        statusbar.appTransitionPending();
53                    }
54                }
55            });
56        }
57
58        @Override
59        public void onAppTransitionStartingLocked(IBinder openToken, IBinder closeToken,
60                final Animation openAnimation, final Animation closeAnimation) {
61            mHandler.post(new Runnable() {
62                @Override
63                public void run() {
64                    StatusBarManagerInternal statusbar = getStatusBarInternal();
65                    if (statusbar != null) {
66                        long startTime = calculateStatusBarTransitionStartTime(openAnimation,
67                                closeAnimation);
68                        long duration = closeAnimation != null || openAnimation != null
69                                ? TRANSITION_DURATION : 0;
70                        statusbar.appTransitionStarting(startTime, duration);
71                    }
72                }
73            });
74        }
75
76        @Override
77        public void onAppTransitionCancelledLocked() {
78            mHandler.post(new Runnable() {
79                @Override
80                public void run() {
81                    StatusBarManagerInternal statusbar = getStatusBarInternal();
82                    if (statusbar != null) {
83                        statusbar.appTransitionCancelled();
84                    }
85                }
86            });
87        }
88
89        @Override
90        public void onAppTransitionFinishedLocked(IBinder token) {
91            mHandler.post(new Runnable() {
92                @Override
93                public void run() {
94                    StatusBarManagerInternal statusbar = LocalServices.getService(
95                            StatusBarManagerInternal.class);
96                    if (statusbar != null) {
97                        statusbar.appTransitionFinished();
98                    }
99                }
100            });
101        }
102    };
103
104    public StatusBarController() {
105        super("StatusBar",
106                View.STATUS_BAR_TRANSIENT,
107                View.STATUS_BAR_UNHIDE,
108                View.STATUS_BAR_TRANSLUCENT,
109                StatusBarManager.WINDOW_STATUS_BAR,
110                FLAG_TRANSLUCENT_STATUS,
111                View.STATUS_BAR_TRANSPARENT);
112    }
113
114    @Override
115    protected boolean skipAnimation() {
116        return mWin.getAttrs().height == MATCH_PARENT;
117    }
118
119    public AppTransitionListener getAppTransitionListener() {
120        return mAppTransitionListener;
121    }
122
123    /**
124     * For a given app transition with {@code openAnimation} and {@code closeAnimation}, this
125     * calculates the timings for the corresponding status bar transition.
126     *
127     * @return the desired start time of the status bar transition, in uptime millis
128     */
129    private static long calculateStatusBarTransitionStartTime(Animation openAnimation,
130            Animation closeAnimation) {
131        if (openAnimation != null && closeAnimation != null) {
132            TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
133            TranslateAnimation closeTranslateAnimation = findTranslateAnimation(closeAnimation);
134            if (openTranslateAnimation != null) {
135
136                // Some interpolators are extremely quickly mostly finished, but not completely. For
137                // our purposes, we need to find the fraction for which ther interpolator is mostly
138                // there, and use that value for the calculation.
139                float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
140                return SystemClock.uptimeMillis()
141                        + openTranslateAnimation.getStartOffset()
142                        + (long)(openTranslateAnimation.getDuration()*t) - TRANSITION_DURATION;
143            } else if (closeTranslateAnimation != null) {
144                return SystemClock.uptimeMillis();
145            } else {
146                return SystemClock.uptimeMillis();
147            }
148        } else {
149            return SystemClock.uptimeMillis();
150        }
151    }
152
153    /**
154     * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
155     *
156     * @return the found animation, {@code null} otherwise
157     */
158    private static TranslateAnimation findTranslateAnimation(Animation animation) {
159        if (animation instanceof TranslateAnimation) {
160            return (TranslateAnimation) animation;
161        } else if (animation instanceof AnimationSet) {
162            AnimationSet set = (AnimationSet) animation;
163            for (int i = 0; i < set.getAnimations().size(); i++) {
164                Animation a = set.getAnimations().get(i);
165                if (a instanceof TranslateAnimation) {
166                    return (TranslateAnimation) a;
167                }
168            }
169        }
170        return null;
171    }
172
173    /**
174     * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
175     * {@code interpolator(t + eps) > 0.99}.
176     */
177    private static float findAlmostThereFraction(Interpolator interpolator) {
178        float val = 0.5f;
179        float adj = 0.25f;
180        while (adj >= 0.01f) {
181            if (interpolator.getInterpolation(val) < 0.99f) {
182                val += adj;
183            } else {
184                val -= adj;
185            }
186            adj /= 2;
187        }
188        return val;
189    }
190}
191