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