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 java.util.Arrays;
     20 
     21 import android.animation.LayoutTransition;
     22 import android.app.Notification;
     23 import android.app.PendingIntent;
     24 import android.content.Context;
     25 import android.content.res.Resources;
     26 import android.graphics.Bitmap;
     27 import android.graphics.PixelFormat;
     28 import android.graphics.drawable.Drawable;
     29 import android.os.Handler;
     30 import android.os.IBinder;
     31 import android.os.Message;
     32 import android.util.Slog;
     33 import android.view.Gravity;
     34 import android.view.LayoutInflater;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 import android.view.WindowManager;
     38 import android.view.WindowManagerImpl;
     39 import android.widget.FrameLayout;
     40 import android.widget.ImageView;
     41 import android.widget.FrameLayout;
     42 import android.widget.TextView;
     43 
     44 import com.android.internal.statusbar.StatusBarIcon;
     45 import com.android.internal.statusbar.StatusBarNotification;
     46 
     47 import com.android.systemui.R;
     48 import com.android.systemui.statusbar.StatusBarIconView;
     49 
     50 public class TabletTicker
     51         extends Handler
     52         implements LayoutTransition.TransitionListener {
     53 
     54     private static final String TAG = "StatusBar.TabletTicker";
     55 
     56     private static final boolean CLICKABLE_TICKER = true;
     57 
     58     // 3 is enough to let us see most cases, but not get so far behind that it's too annoying.
     59     private static final int QUEUE_LENGTH = 3;
     60 
     61     private static final int MSG_ADVANCE = 1;
     62 
     63     private static final int ADVANCE_DELAY = 5000; // 5 seconds
     64 
     65     private Context mContext;
     66 
     67     private ViewGroup mWindow;
     68     private IBinder mCurrentKey;
     69     private StatusBarNotification mCurrentNotification;
     70     private View mCurrentView;
     71 
     72     private IBinder[] mKeys = new IBinder[QUEUE_LENGTH];
     73     private StatusBarNotification[] mQueue = new StatusBarNotification[QUEUE_LENGTH];
     74     private int mQueuePos;
     75 
     76     private final int mLargeIconHeight;
     77 
     78     private TabletStatusBar mBar;
     79 
     80     private LayoutTransition mLayoutTransition;
     81     private boolean mWindowShouldClose;
     82 
     83     public TabletTicker(TabletStatusBar bar) {
     84         mBar = bar;
     85         mContext = bar.getContext();
     86         final Resources res = mContext.getResources();
     87         mLargeIconHeight = res.getDimensionPixelSize(
     88                 android.R.dimen.notification_large_icon_height);
     89     }
     90 
     91     public void add(IBinder key, StatusBarNotification notification) {
     92         if (false) {
     93             Slog.d(TAG, "add 1 mCurrentNotification=" + mCurrentNotification
     94                     + " mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue));
     95         }
     96 
     97         // If it's already in here, remove whatever's in there and put the new one at the end.
     98         remove(key, false);
     99 
    100         mKeys[mQueuePos] = key;
    101         mQueue[mQueuePos] = notification;
    102 
    103         // If nothing is running now, start the next one.
    104         if (mQueuePos == 0 && mCurrentNotification == null) {
    105             sendEmptyMessage(MSG_ADVANCE);
    106         }
    107 
    108         if (mQueuePos < QUEUE_LENGTH - 1) {
    109             mQueuePos++;
    110         }
    111     }
    112 
    113     public void remove(IBinder key) {
    114         remove(key, true);
    115     }
    116 
    117     public void remove(IBinder key, boolean advance) {
    118         if (mCurrentKey == key) {
    119             // Showing now
    120             if (advance) {
    121                 removeMessages(MSG_ADVANCE);
    122                 sendEmptyMessage(MSG_ADVANCE);
    123             }
    124         } else {
    125             // In the queue
    126             for (int i=0; i<QUEUE_LENGTH; i++) {
    127                 if (mKeys[i] == key) {
    128                     for (; i<QUEUE_LENGTH-1; i++) {
    129                         mKeys[i] = mKeys[i+1];
    130                         mQueue[i] = mQueue[i+1];
    131                     }
    132                     mKeys[QUEUE_LENGTH-1] = null;
    133                     mQueue[QUEUE_LENGTH-1] = null;
    134                     if (mQueuePos > 0) {
    135                         mQueuePos--;
    136                     }
    137                     break;
    138                 }
    139             }
    140         }
    141     }
    142 
    143     public void halt() {
    144         removeMessages(MSG_ADVANCE);
    145         if (mCurrentView != null || mQueuePos != 0) {
    146             for (int i=0; i<QUEUE_LENGTH; i++) {
    147                 mKeys[i] = null;
    148                 mQueue[i] = null;
    149             }
    150             mQueuePos = 0;
    151             sendEmptyMessage(MSG_ADVANCE);
    152         }
    153     }
    154 
    155     public void handleMessage(Message msg) {
    156         switch (msg.what) {
    157             case MSG_ADVANCE:
    158                 advance();
    159                 break;
    160         }
    161     }
    162 
    163     private void advance() {
    164         // Out with the old...
    165         if (mCurrentView != null) {
    166             if (mWindow != null) {
    167                 mWindow.removeView(mCurrentView);
    168             }
    169             mCurrentView = null;
    170             mCurrentKey = null;
    171             mCurrentNotification = null;
    172         }
    173 
    174         // In with the new...
    175         dequeue();
    176         while (mCurrentNotification != null) {
    177             mCurrentView = makeTickerView(mCurrentNotification);
    178             if (mCurrentView != null) {
    179                 if (mWindow == null) {
    180                     mWindow = makeWindow();
    181                     WindowManagerImpl.getDefault().addView(mWindow, mWindow.getLayoutParams());
    182                 }
    183 
    184                 mWindow.addView(mCurrentView);
    185                 sendEmptyMessageDelayed(MSG_ADVANCE, ADVANCE_DELAY);
    186                 break;
    187             }
    188             dequeue();
    189         }
    190 
    191         // if there's nothing left, close the window
    192         mWindowShouldClose = (mCurrentView == null && mWindow != null);
    193     }
    194 
    195     private void dequeue() {
    196         mCurrentKey = mKeys[0];
    197         mCurrentNotification = mQueue[0];
    198         if (false) {
    199             Slog.d(TAG, "dequeue mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue));
    200         }
    201         final int N = mQueuePos;
    202         for (int i=0; i<N; i++) {
    203             mKeys[i] = mKeys[i+1];
    204             mQueue[i] = mQueue[i+1];
    205         }
    206         mKeys[N] = null;
    207         mQueue[N] = null;
    208         if (mQueuePos > 0) {
    209             mQueuePos--;
    210         }
    211     }
    212 
    213     private ViewGroup makeWindow() {
    214         final Resources res = mContext.getResources();
    215         final FrameLayout view = new FrameLayout(mContext);
    216         final int width = res.getDimensionPixelSize(R.dimen.notification_ticker_width);
    217         int windowFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    218                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    219                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
    220         if (CLICKABLE_TICKER) {
    221             windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
    222         } else {
    223             windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    224         }
    225         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, mLargeIconHeight,
    226                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, windowFlags,
    227                 PixelFormat.TRANSLUCENT);
    228         lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
    229 //        lp.windowAnimations = com.android.internal.R.style.Animation_Toast;
    230 
    231         mLayoutTransition = new LayoutTransition();
    232         mLayoutTransition.addTransitionListener(this);
    233         view.setLayoutTransition(mLayoutTransition);
    234         lp.setTitle("NotificationTicker");
    235         view.setLayoutParams(lp);
    236         return view;
    237     }
    238 
    239     public void startTransition(LayoutTransition transition, ViewGroup container,
    240             View view, int transitionType) {}
    241 
    242     public void endTransition(LayoutTransition transition, ViewGroup container,
    243             View view, int transitionType) {
    244         if (mWindowShouldClose) {
    245             WindowManagerImpl.getDefault().removeView(mWindow);
    246             mWindow = null;
    247             mWindowShouldClose = false;
    248             mBar.doneTicking();
    249         }
    250     }
    251 
    252     private View makeTickerView(StatusBarNotification notification) {
    253         final Notification n = notification.notification;
    254 
    255         LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
    256                 Context.LAYOUT_INFLATER_SERVICE);
    257 
    258         ViewGroup group;
    259         int layoutId;
    260         int iconId;
    261         if (n.largeIcon != null) {
    262             iconId = R.id.right_icon;
    263         } else {
    264             iconId = R.id.left_icon;
    265         }
    266         if (n.tickerView != null) {
    267             group = (ViewGroup)inflater.inflate(R.layout.status_bar_ticker_panel, null, false);
    268             ViewGroup content = (FrameLayout) group.findViewById(R.id.ticker_expanded);
    269             View expanded = null;
    270             Exception exception = null;
    271             try {
    272                 expanded = n.tickerView.apply(mContext, content);
    273             }
    274             catch (RuntimeException e) {
    275                 exception = e;
    276             }
    277             if (expanded == null) {
    278                 final String ident = notification.pkg
    279                         + "/0x" + Integer.toHexString(notification.id);
    280                 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
    281                 return null;
    282             }
    283             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
    284                     ViewGroup.LayoutParams.MATCH_PARENT,
    285                     ViewGroup.LayoutParams.MATCH_PARENT);
    286             content.addView(expanded, lp);
    287         } else if (n.tickerText != null) {
    288             group = (ViewGroup)inflater.inflate(R.layout.status_bar_ticker_compat, mWindow, false);
    289             final Drawable icon = StatusBarIconView.getIcon(mContext,
    290                     new StatusBarIcon(notification.pkg, n.icon, n.iconLevel, 0, n.tickerText));
    291             ImageView iv = (ImageView)group.findViewById(iconId);
    292             iv.setImageDrawable(icon);
    293             iv.setVisibility(View.VISIBLE);
    294             TextView tv = (TextView)group.findViewById(R.id.text);
    295             tv.setText(n.tickerText);
    296         } else {
    297             throw new RuntimeException("tickerView==null && tickerText==null");
    298         }
    299         ImageView largeIcon = (ImageView)group.findViewById(R.id.large_icon);
    300         if (n.largeIcon != null) {
    301             largeIcon.setImageBitmap(n.largeIcon);
    302             largeIcon.setVisibility(View.VISIBLE);
    303             final ViewGroup.LayoutParams lp = largeIcon.getLayoutParams();
    304             final int statusBarHeight = mBar.getStatusBarHeight();
    305             if (n.largeIcon.getHeight() <= statusBarHeight) {
    306                 // for smallish largeIcons, it looks a little odd to have them floating halfway up
    307                 // the ticker, so we vertically center them in the status bar area instead
    308                 lp.height = statusBarHeight;
    309             } else {
    310                 lp.height = mLargeIconHeight;
    311             }
    312             largeIcon.setLayoutParams(lp);
    313         }
    314 
    315         if (CLICKABLE_TICKER) {
    316             PendingIntent contentIntent = notification.notification.contentIntent;
    317             if (contentIntent != null) {
    318                 // create the usual notification clicker, but chain it together with a halt() call
    319                 // to abort the ticker too
    320                 final View.OnClickListener clicker = mBar.makeClicker(contentIntent,
    321                                             notification.pkg, notification.tag, notification.id);
    322                 group.setOnClickListener(new View.OnClickListener() {
    323                     public void onClick(View v) {
    324                         halt();
    325                         clicker.onClick(v);
    326                     }
    327                 });
    328             } else {
    329                 group.setOnClickListener(null);
    330             }
    331         }
    332 
    333         return group;
    334     }
    335 }
    336 
    337