• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.tablet;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.content.Context;
24 import android.graphics.Rect;
25 import android.util.AttributeSet;
26 import android.util.Slog;
27 import android.view.LayoutInflater;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.ViewTreeObserver;
32 import android.view.animation.AccelerateInterpolator;
33 import android.view.animation.DecelerateInterpolator;
34 import android.view.animation.Interpolator;
35 import android.widget.RelativeLayout;
36 
37 import com.android.systemui.R;
38 
39 public class NotificationPanel extends RelativeLayout implements StatusBarPanel,
40         View.OnClickListener {
41     static final String TAG = "Tablet/NotificationPanel";
42     static final boolean DEBUG = false;
43 
44     final static int PANEL_FADE_DURATION = 150;
45 
46     boolean mShowing;
47     boolean mHasClearableNotifications = false;
48     int mNotificationCount = 0;
49     NotificationPanelTitle mTitleArea;
50     View mSettingsButton;
51     View mNotificationButton;
52     View mNotificationScroller;
53     ViewGroup mContentFrame;
54     Rect mContentArea = new Rect();
55     View mSettingsView;
56     ViewGroup mContentParent;
57     TabletStatusBar mBar;
58     View mClearButton;
59     static Interpolator sAccelerateInterpolator = new AccelerateInterpolator();
60     static Interpolator sDecelerateInterpolator = new DecelerateInterpolator();
61 
62     // amount to slide mContentParent down by when mContentFrame is missing
63     float mContentFrameMissingTranslation;
64 
65     Choreographer mChoreo = new Choreographer();
66 
NotificationPanel(Context context, AttributeSet attrs)67     public NotificationPanel(Context context, AttributeSet attrs) {
68         this(context, attrs, 0);
69     }
70 
NotificationPanel(Context context, AttributeSet attrs, int defStyle)71     public NotificationPanel(Context context, AttributeSet attrs, int defStyle) {
72         super(context, attrs, defStyle);
73     }
74 
setBar(TabletStatusBar b)75     public void setBar(TabletStatusBar b) {
76         mBar = b;
77     }
78 
79     @Override
onFinishInflate()80     public void onFinishInflate() {
81         super.onFinishInflate();
82 
83         setWillNotDraw(false);
84 
85         mContentParent = (ViewGroup)findViewById(R.id.content_parent);
86         mContentParent.bringToFront();
87         mTitleArea = (NotificationPanelTitle) findViewById(R.id.title_area);
88         mTitleArea.setPanel(this);
89 
90         mSettingsButton = findViewById(R.id.settings_button);
91         mNotificationButton = findViewById(R.id.notification_button);
92 
93         mNotificationScroller = findViewById(R.id.notification_scroller);
94         mContentFrame = (ViewGroup)findViewById(R.id.content_frame);
95         mContentFrameMissingTranslation = 0; // not needed with current assets
96 
97         // the "X" that appears in place of the clock when the panel is showing notifications
98         mClearButton = findViewById(R.id.clear_all_button);
99         mClearButton.setOnClickListener(mClearButtonListener);
100 
101         mShowing = false;
102 
103         setContentFrameVisible(mNotificationCount > 0, false);
104     }
105 
106     private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
107         public void onClick(View v) {
108             mBar.clearAll();
109         }
110     };
111 
getClearButton()112     public View getClearButton() {
113         return mClearButton;
114     }
115 
show(boolean show, boolean animate)116     public void show(boolean show, boolean animate) {
117         if (show && !mShowing) {
118             setContentFrameVisible(mSettingsView != null || mNotificationCount > 0, false);
119         }
120 
121         if (animate) {
122             if (mShowing != show) {
123                 mShowing = show;
124                 if (show) {
125                     setVisibility(View.VISIBLE);
126                     // Don't start the animation until we've created the layer, which is done
127                     // right before we are drawn
128                     mContentParent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
129                     getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
130                 } else {
131                     mChoreo.startAnimation(show);
132                 }
133             }
134         } else {
135             mShowing = show;
136             setVisibility(show ? View.VISIBLE : View.GONE);
137         }
138     }
139 
140     /**
141      * This is used only when we've created a hardware layer and are waiting until it's
142      * been created in order to start the appearing animation.
143      */
144     private ViewTreeObserver.OnPreDrawListener mPreDrawListener =
145             new ViewTreeObserver.OnPreDrawListener() {
146         @Override
147         public boolean onPreDraw() {
148             getViewTreeObserver().removeOnPreDrawListener(this);
149             mChoreo.startAnimation(true);
150             return false;
151         }
152     };
153 
154     /**
155      * Whether the panel is showing, or, if it's animating, whether it will be
156      * when the animation is done.
157      */
isShowing()158     public boolean isShowing() {
159         return mShowing;
160     }
161 
162     @Override
onVisibilityChanged(View v, int vis)163     public void onVisibilityChanged(View v, int vis) {
164         super.onVisibilityChanged(v, vis);
165         // when we hide, put back the notifications
166         if (vis != View.VISIBLE) {
167             if (mSettingsView != null) removeSettingsView();
168             mNotificationScroller.setVisibility(View.VISIBLE);
169             mNotificationScroller.setAlpha(1f);
170             mNotificationScroller.scrollTo(0, 0);
171             updatePanelModeButtons();
172         }
173     }
174 
175     @Override
dispatchHoverEvent(MotionEvent event)176     public boolean dispatchHoverEvent(MotionEvent event) {
177         // Ignore hover events outside of this panel bounds since such events
178         // generate spurious accessibility events with the panel content when
179         // tapping outside of it, thus confusing the user.
180         final int x = (int) event.getX();
181         final int y = (int) event.getY();
182         if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
183             return super.dispatchHoverEvent(event);
184         }
185         return true;
186     }
187 
188     /*
189     @Override
190     protected void onLayout(boolean changed, int l, int t, int r, int b) {
191         super.onLayout(changed, l, t, r, b);
192 
193         if (DEBUG) Slog.d(TAG, String.format("PANEL: onLayout: (%d, %d, %d, %d)", l, t, r, b));
194     }
195 
196     @Override
197     public void onSizeChanged(int w, int h, int oldw, int oldh) {
198         super.onSizeChanged(w, h, oldw, oldh);
199 
200         if (DEBUG) {
201             Slog.d(TAG, String.format("PANEL: onSizeChanged: (%d -> %d, %d -> %d)",
202                         oldw, w, oldh, h));
203         }
204     }
205     */
206 
onClick(View v)207     public void onClick(View v) {
208         if (v == mTitleArea) {
209             swapPanels();
210         }
211     }
212 
setNotificationCount(int n)213     public void setNotificationCount(int n) {
214 //        Slog.d(TAG, "notificationCount=" + n);
215         if (!mShowing) {
216             // just do it, already
217             setContentFrameVisible(n > 0, false);
218         } else if (mSettingsView == null) {
219             // we're looking at the notifications; time to maybe make some changes
220             if ((mNotificationCount > 0) != (n > 0)) {
221                 setContentFrameVisible(n > 0, true);
222             }
223         }
224         mNotificationCount = n;
225     }
226 
setContentFrameVisible(final boolean showing, boolean animate)227     public void setContentFrameVisible(final boolean showing, boolean animate) {
228         if (!animate) {
229             mContentFrame.setVisibility(showing ? View.VISIBLE : View.GONE);
230             mContentFrame.setAlpha(1f);
231             // the translation will be patched up when the window is slid into place
232             return;
233         }
234 
235         if (showing) {
236             mContentFrame.setVisibility(View.VISIBLE);
237         }
238         AnimatorSet set = new AnimatorSet();
239         set.play(ObjectAnimator.ofFloat(
240                 mContentFrame, "alpha",
241                 showing ? 0f : 1f,
242                 showing ? 1f : 0f))
243             .with(ObjectAnimator.ofFloat(
244                 mContentParent, "translationY",
245                 showing ? mContentFrameMissingTranslation : 0f,
246                 showing ? 0f : mContentFrameMissingTranslation))
247               ;
248 
249         set.setDuration(200);
250         set.addListener(new AnimatorListenerAdapter() {
251             @Override
252             public void onAnimationEnd(Animator _a) {
253                 if (!showing) {
254                     mContentFrame.setVisibility(View.GONE);
255                     mContentFrame.setAlpha(1f);
256                 }
257                 updateClearButton();
258             }
259         });
260         set.start();
261     }
262 
swapPanels()263     public void swapPanels() {
264         final View toShow, toHide;
265         if (mSettingsView == null) {
266             addSettingsView();
267             toShow = mSettingsView;
268             toHide = mNotificationScroller;
269         } else {
270             toShow = mNotificationScroller;
271             toHide = mSettingsView;
272         }
273         Animator a = ObjectAnimator.ofFloat(toHide, "alpha", 1f, 0f)
274                 .setDuration(PANEL_FADE_DURATION);
275         a.addListener(new AnimatorListenerAdapter() {
276             @Override
277             public void onAnimationEnd(Animator _a) {
278                 toHide.setVisibility(View.GONE);
279                 if (toShow != null) {
280                     if (mNotificationCount == 0) {
281                         // show the frame for settings, hide for notifications
282                         setContentFrameVisible(toShow == mSettingsView, true);
283                     }
284 
285                     toShow.setVisibility(View.VISIBLE);
286                     if (toShow == mSettingsView || mNotificationCount > 0) {
287                         ObjectAnimator.ofFloat(toShow, "alpha", 0f, 1f)
288                                 .setDuration(PANEL_FADE_DURATION)
289                                 .start();
290                     }
291 
292                     if (toHide == mSettingsView) {
293                         removeSettingsView();
294                     }
295                 }
296                 updateClearButton();
297                 updatePanelModeButtons();
298             }
299         });
300         a.start();
301     }
302 
updateClearButton()303     public void updateClearButton() {
304         if (mBar != null) {
305             final boolean showX
306                 = (isShowing()
307                         && mHasClearableNotifications
308                         && mNotificationScroller.getVisibility() == View.VISIBLE);
309             getClearButton().setVisibility(showX ? View.VISIBLE : View.INVISIBLE);
310         }
311     }
312 
setClearable(boolean clearable)313     public void setClearable(boolean clearable) {
314         mHasClearableNotifications = clearable;
315     }
316 
updatePanelModeButtons()317     public void updatePanelModeButtons() {
318         final boolean settingsVisible = (mSettingsView != null);
319         mSettingsButton.setVisibility(!settingsVisible ? View.VISIBLE : View.INVISIBLE);
320         mNotificationButton.setVisibility(settingsVisible ? View.VISIBLE : View.INVISIBLE);
321     }
322 
isInContentArea(int x, int y)323     public boolean isInContentArea(int x, int y) {
324         mContentArea.left = mTitleArea.getLeft() + mTitleArea.getPaddingLeft();
325         mContentArea.top = mTitleArea.getTop() + mTitleArea.getPaddingTop()
326             + (int)mContentParent.getTranslationY(); // account for any adjustment
327         mContentArea.right = mTitleArea.getRight() - mTitleArea.getPaddingRight();
328 
329         View theBottom = (mContentFrame.getVisibility() == View.VISIBLE)
330             ? mContentFrame : mTitleArea;
331         mContentArea.bottom = theBottom.getBottom() - theBottom.getPaddingBottom();
332 
333         offsetDescendantRectToMyCoords(mContentParent, mContentArea);
334         return mContentArea.contains(x, y);
335     }
336 
removeSettingsView()337     void removeSettingsView() {
338         if (mSettingsView != null) {
339             mContentFrame.removeView(mSettingsView);
340             mSettingsView = null;
341         }
342     }
343 
344     // NB: it will be invisible until you show it
addSettingsView()345     void addSettingsView() {
346         LayoutInflater infl = LayoutInflater.from(getContext());
347         mSettingsView = infl.inflate(R.layout.status_bar_settings_view, mContentFrame, false);
348         mSettingsView.setVisibility(View.GONE);
349         mContentFrame.addView(mSettingsView);
350     }
351 
352     private class Choreographer implements Animator.AnimatorListener {
353         boolean mVisible;
354         int mPanelHeight;
355         AnimatorSet mContentAnim;
356 
357         // should group this into a multi-property animation
358         final static int OPEN_DURATION = 250;
359         final static int CLOSE_DURATION = 250;
360 
361         // the panel will start to appear this many px from the end
362         final int HYPERSPACE_OFFRAMP = 200;
363 
Choreographer()364         Choreographer() {
365         }
366 
createAnimation(boolean appearing)367         void createAnimation(boolean appearing) {
368             // mVisible: previous state; appearing: new state
369 
370             float start, end;
371 
372             // 0: on-screen
373             // height: off-screen
374             float y = mContentParent.getTranslationY();
375             if (appearing) {
376                 // we want to go from near-the-top to the top, unless we're half-open in the right
377                 // general vicinity
378                 end = 0;
379                 if (mNotificationCount == 0) {
380                     end += mContentFrameMissingTranslation;
381                 }
382                 start = HYPERSPACE_OFFRAMP+end;
383             } else {
384                 start = y;
385                 end = y + HYPERSPACE_OFFRAMP;
386             }
387 
388             Animator posAnim = ObjectAnimator.ofFloat(mContentParent, "translationY",
389                     start, end);
390             posAnim.setInterpolator(appearing ? sDecelerateInterpolator : sAccelerateInterpolator);
391 
392             if (mContentAnim != null && mContentAnim.isRunning()) {
393                 mContentAnim.cancel();
394             }
395 
396             Animator fadeAnim = ObjectAnimator.ofFloat(mContentParent, "alpha",
397                     appearing ? 1.0f : 0.0f);
398             fadeAnim.setInterpolator(appearing ? sAccelerateInterpolator : sDecelerateInterpolator);
399 
400             mContentAnim = new AnimatorSet();
401             mContentAnim
402                 .play(fadeAnim)
403                 .with(posAnim)
404                 ;
405             mContentAnim.setDuration((DEBUG?10:1)*(appearing ? OPEN_DURATION : CLOSE_DURATION));
406             mContentAnim.addListener(this);
407         }
408 
startAnimation(boolean appearing)409         void startAnimation(boolean appearing) {
410             if (DEBUG) Slog.d(TAG, "startAnimation(appearing=" + appearing + ")");
411 
412             createAnimation(appearing);
413             mContentAnim.start();
414 
415             mVisible = appearing;
416 
417             // we want to start disappearing promptly
418             if (!mVisible) updateClearButton();
419         }
420 
onAnimationCancel(Animator animation)421         public void onAnimationCancel(Animator animation) {
422             if (DEBUG) Slog.d(TAG, "onAnimationCancel");
423         }
424 
onAnimationEnd(Animator animation)425         public void onAnimationEnd(Animator animation) {
426             if (DEBUG) Slog.d(TAG, "onAnimationEnd");
427             if (! mVisible) {
428                 setVisibility(View.GONE);
429             }
430             mContentParent.setLayerType(View.LAYER_TYPE_NONE, null);
431             mContentAnim = null;
432 
433             // we want to show the X lazily
434             if (mVisible) updateClearButton();
435         }
436 
onAnimationRepeat(Animator animation)437         public void onAnimationRepeat(Animator animation) {
438         }
439 
onAnimationStart(Animator animation)440         public void onAnimationStart(Animator animation) {
441         }
442     }
443 }
444 
445