Home | History | Annotate | Download | only in tablet
      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 
     75     public NotificationPanel(Context context, AttributeSet attrs) {
     76         this(context, attrs, 0);
     77     }
     78 
     79     public NotificationPanel(Context context, AttributeSet attrs, int defStyle) {
     80         super(context, attrs, defStyle);
     81     }
     82 
     83     public void setBar(TabletStatusBar b) {
     84         mBar = b;
     85     }
     86 
     87     @Override
     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
    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 
    129     public View getClearButton() {
    130         return mClearButton;
    131     }
    132 
    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      */
    171     public boolean isShowing() {
    172         return mShowing;
    173     }
    174 
    175     @Override
    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
    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
    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 
    241     public void onClick(View v) {
    242         if (mSettingsButton.isEnabled() && v == mTitleArea) {
    243             swapPanels();
    244         }
    245     }
    246 
    247     public void setNotificationCount(int n) {
    248         mNotificationCount = n;
    249     }
    250 
    251     public void setContentFrameVisible(final boolean showing, boolean animate) {
    252     }
    253 
    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 
    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 
    299     public void setClearable(boolean clearable) {
    300         mHasClearableNotifications = clearable;
    301     }
    302 
    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 
    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 
    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
    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 
    347         Choreographer() {
    348         }
    349 
    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 
    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 
    404         public void onAnimationCancel(Animator animation) {
    405             if (DEBUG) Slog.d(TAG, "onAnimationCancel");
    406         }
    407 
    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 
    420         public void onAnimationRepeat(Animator animation) {
    421         }
    422 
    423         public void onAnimationStart(Animator animation) {
    424         }
    425     }
    426 
    427     @Override
    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
    441     public boolean onTouchEvent(MotionEvent ev) {
    442         boolean handled = mExpandHelper.onTouchEvent(ev) ||
    443                 super.onTouchEvent(ev);
    444         return handled;
    445     }
    446 
    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 
    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