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.service.notification.StatusBarNotification;
     25 import android.content.Context;
     26 import android.content.res.Resources;
     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.widget.FrameLayout;
     39 import android.widget.ImageView;
     40 import android.widget.TextView;
     41 
     42 import com.android.internal.statusbar.StatusBarIcon;
     43 
     44 import com.android.systemui.R;
     45 import com.android.systemui.statusbar.StatusBarIconView;
     46 
     47 public class TabletTicker
     48         extends Handler
     49         implements LayoutTransition.TransitionListener {
     50 
     51     private static final String TAG = "StatusBar.TabletTicker";
     52 
     53     private static final boolean CLICKABLE_TICKER = true;
     54 
     55     // 3 is enough to let us see most cases, but not get so far behind that it's too annoying.
     56     private static final int QUEUE_LENGTH = 3;
     57 
     58     private static final int MSG_ADVANCE = 1;
     59 
     60     private static final int ADVANCE_DELAY = 5000; // 5 seconds
     61 
     62     private final Context mContext;
     63     private final WindowManager mWindowManager;
     64 
     65     private ViewGroup mWindow;
     66     private IBinder mCurrentKey;
     67     private StatusBarNotification mCurrentNotification;
     68     private View mCurrentView;
     69 
     70     private IBinder[] mKeys = new IBinder[QUEUE_LENGTH];
     71     private StatusBarNotification[] mQueue = new StatusBarNotification[QUEUE_LENGTH];
     72     private int mQueuePos;
     73 
     74     private final int mLargeIconHeight;
     75 
     76     private TabletStatusBar mBar;
     77 
     78     private LayoutTransition mLayoutTransition;
     79     private boolean mWindowShouldClose;
     80 
     81     public TabletTicker(TabletStatusBar bar) {
     82         mBar = bar;
     83         mContext = bar.getContext();
     84         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
     85         final Resources res = mContext.getResources();
     86         mLargeIconHeight = res.getDimensionPixelSize(
     87                 android.R.dimen.notification_large_icon_height);
     88     }
     89 
     90     public void add(IBinder key, StatusBarNotification notification) {
     91         if (false) {
     92             Slog.d(TAG, "add 1 mCurrentNotification=" + mCurrentNotification
     93                     + " mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue));
     94         }
     95 
     96         // If it's already in here, remove whatever's in there and put the new one at the end.
     97         remove(key, false);
     98 
     99         mKeys[mQueuePos] = key;
    100         mQueue[mQueuePos] = notification;
    101 
    102         // If nothing is running now, start the next one.
    103         if (mQueuePos == 0 && mCurrentNotification == null) {
    104             sendEmptyMessage(MSG_ADVANCE);
    105         }
    106 
    107         if (mQueuePos < QUEUE_LENGTH - 1) {
    108             mQueuePos++;
    109         }
    110     }
    111 
    112     public void remove(IBinder key) {
    113         remove(key, true);
    114     }
    115 
    116     public void remove(IBinder key, boolean advance) {
    117         if (mCurrentKey == key) {
    118             // Showing now
    119             if (advance) {
    120                 removeMessages(MSG_ADVANCE);
    121                 sendEmptyMessage(MSG_ADVANCE);
    122             }
    123         } else {
    124             // In the queue
    125             for (int i=0; i<QUEUE_LENGTH; i++) {
    126                 if (mKeys[i] == key) {
    127                     for (; i<QUEUE_LENGTH-1; i++) {
    128                         mKeys[i] = mKeys[i+1];
    129                         mQueue[i] = mQueue[i+1];
    130                     }
    131                     mKeys[QUEUE_LENGTH-1] = null;
    132                     mQueue[QUEUE_LENGTH-1] = null;
    133                     if (mQueuePos > 0) {
    134                         mQueuePos--;
    135                     }
    136                     break;
    137                 }
    138             }
    139         }
    140     }
    141 
    142     public void halt() {
    143         removeMessages(MSG_ADVANCE);
    144         if (mCurrentView != null || mQueuePos != 0) {
    145             for (int i=0; i<QUEUE_LENGTH; i++) {
    146                 mKeys[i] = null;
    147                 mQueue[i] = null;
    148             }
    149             mQueuePos = 0;
    150             sendEmptyMessage(MSG_ADVANCE);
    151         }
    152     }
    153 
    154     public void handleMessage(Message msg) {
    155         switch (msg.what) {
    156             case MSG_ADVANCE:
    157                 advance();
    158                 break;
    159         }
    160     }
    161 
    162     private void advance() {
    163         // Out with the old...
    164         if (mCurrentView != null) {
    165             if (mWindow != null) {
    166                 mWindow.removeView(mCurrentView);
    167             }
    168             mCurrentView = null;
    169             mCurrentKey = null;
    170             mCurrentNotification = null;
    171         }
    172 
    173         // In with the new...
    174         dequeue();
    175         while (mCurrentNotification != null) {
    176             mCurrentView = makeTickerView(mCurrentNotification);
    177             if (mCurrentView != null) {
    178                 if (mWindow == null) {
    179                     mWindow = makeWindow();
    180                     mWindowManager.addView(mWindow, mWindow.getLayoutParams());
    181                 }
    182 
    183                 mWindow.addView(mCurrentView);
    184                 sendEmptyMessageDelayed(MSG_ADVANCE, ADVANCE_DELAY);
    185                 break;
    186             }
    187             dequeue();
    188         }
    189 
    190         // if there's nothing left, close the window
    191         mWindowShouldClose = (mCurrentView == null && mWindow != null);
    192     }
    193 
    194     private void dequeue() {
    195         mCurrentKey = mKeys[0];
    196         mCurrentNotification = mQueue[0];
    197         if (false) {
    198             Slog.d(TAG, "dequeue mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue));
    199         }
    200         final int N = mQueuePos;
    201         for (int i=0; i<N; i++) {
    202             mKeys[i] = mKeys[i+1];
    203             mQueue[i] = mQueue[i+1];
    204         }
    205         mKeys[N] = null;
    206         mQueue[N] = null;
    207         if (mQueuePos > 0) {
    208             mQueuePos--;
    209         }
    210     }
    211 
    212     private ViewGroup makeWindow() {
    213         final Resources res = mContext.getResources();
    214         final FrameLayout view = new FrameLayout(mContext);
    215         final int width = res.getDimensionPixelSize(R.dimen.notification_ticker_width);
    216         int windowFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    217                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    218                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
    219         if (CLICKABLE_TICKER) {
    220             windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
    221         } else {
    222             windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    223         }
    224         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, mLargeIconHeight,
    225                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, windowFlags,
    226                 PixelFormat.TRANSLUCENT);
    227         lp.gravity = Gravity.BOTTOM | Gravity.END;
    228 //        lp.windowAnimations = com.android.internal.R.style.Animation_Toast;
    229 
    230         mLayoutTransition = new LayoutTransition();
    231         mLayoutTransition.addTransitionListener(this);
    232         view.setLayoutTransition(mLayoutTransition);
    233         lp.setTitle("NotificationTicker");
    234         view.setLayoutParams(lp);
    235         return view;
    236     }
    237 
    238     public void startTransition(LayoutTransition transition, ViewGroup container,
    239             View view, int transitionType) {}
    240 
    241     public void endTransition(LayoutTransition transition, ViewGroup container,
    242             View view, int transitionType) {
    243         if (mWindowShouldClose) {
    244             mWindowManager.removeView(mWindow);
    245             mWindow = null;
    246             mWindowShouldClose = false;
    247             mBar.doneTicking();
    248         }
    249     }
    250 
    251     private View makeTickerView(StatusBarNotification notification) {
    252         final Notification n = notification.getNotification();
    253 
    254         LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
    255                 Context.LAYOUT_INFLATER_SERVICE);
    256 
    257         ViewGroup group;
    258         int layoutId;
    259         int iconId;
    260         if (n.largeIcon != null) {
    261             iconId = R.id.right_icon;
    262         } else {
    263             iconId = R.id.left_icon;
    264         }
    265         if (n.tickerView != null) {
    266             group = (ViewGroup)inflater.inflate(R.layout.system_bar_ticker_panel, null, false);
    267             ViewGroup content = (FrameLayout) group.findViewById(R.id.ticker_expanded);
    268             View expanded = null;
    269             Exception exception = null;
    270             try {
    271                 expanded = n.tickerView.apply(mContext, content);
    272             }
    273             catch (RuntimeException e) {
    274                 exception = e;
    275             }
    276             if (expanded == null) {
    277                 final String ident = notification.getPackageName()
    278                         + "/0x" + Integer.toHexString(notification.getId());
    279                 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
    280                 return null;
    281             }
    282             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
    283                     ViewGroup.LayoutParams.MATCH_PARENT,
    284                     ViewGroup.LayoutParams.MATCH_PARENT);
    285             content.addView(expanded, lp);
    286         } else if (n.tickerText != null) {
    287             group = (ViewGroup)inflater.inflate(R.layout.system_bar_ticker_compat, mWindow, false);
    288             final Drawable icon = StatusBarIconView.getIcon(mContext,
    289                     new StatusBarIcon(notification.getPackageName(), notification.getUser(), n.icon, n.iconLevel, 0,
    290                             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.getNotification().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.getPackageName(), notification.getTag(), notification.getId());
    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