TabletTicker.java revision f68b500bb0d4c24ccabb40284f97981d50f888a8
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 17package com.android.systemui.statusbar.tablet; 18 19import java.util.Arrays; 20 21import android.animation.LayoutTransition; 22import android.app.Notification; 23import android.app.PendingIntent; 24import android.content.Context; 25import android.content.res.Resources; 26import android.graphics.Bitmap; 27import android.graphics.PixelFormat; 28import android.graphics.drawable.Drawable; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.Message; 32import android.util.Slog; 33import android.view.Gravity; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.WindowManager; 38import android.view.WindowManagerImpl; 39import android.widget.FrameLayout; 40import android.widget.ImageView; 41import android.widget.LinearLayout; 42import android.widget.TextView; 43 44import com.android.internal.statusbar.StatusBarIcon; 45import com.android.internal.statusbar.StatusBarNotification; 46 47import com.android.systemui.R; 48import com.android.systemui.statusbar.StatusBarIconView; 49 50public 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 = false; 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 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 } 85 86 public void add(IBinder key, StatusBarNotification notification) { 87 if (false) { 88 Slog.d(TAG, "add 1 mCurrentNotification=" + mCurrentNotification 89 + " mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue)); 90 } 91 92 // If it's already in here, remove whatever's in there and put the new one at the end. 93 remove(key, false); 94 95 mKeys[mQueuePos] = key; 96 mQueue[mQueuePos] = notification; 97 98 // If nothing is running now, start the next one. 99 if (mQueuePos == 0 && mCurrentNotification == null) { 100 sendEmptyMessage(MSG_ADVANCE); 101 } 102 103 if (mQueuePos < QUEUE_LENGTH - 1) { 104 mQueuePos++; 105 } 106 } 107 108 public void remove(IBinder key) { 109 remove(key, true); 110 } 111 112 public void remove(IBinder key, boolean advance) { 113 if (mCurrentKey == key) { 114 // Showing now 115 if (advance) { 116 removeMessages(MSG_ADVANCE); 117 sendEmptyMessage(MSG_ADVANCE); 118 } 119 } else { 120 // In the queue 121 for (int i=0; i<QUEUE_LENGTH; i++) { 122 if (mKeys[i] == key) { 123 for (; i<QUEUE_LENGTH-1; i++) { 124 mKeys[i] = mKeys[i+1]; 125 mQueue[i] = mQueue[i+1]; 126 } 127 mKeys[QUEUE_LENGTH-1] = null; 128 mQueue[QUEUE_LENGTH-1] = null; 129 if (mQueuePos > 0) { 130 mQueuePos--; 131 } 132 break; 133 } 134 } 135 } 136 } 137 138 public void halt() { 139 removeMessages(MSG_ADVANCE); 140 if (mCurrentView != null || mQueuePos != 0) { 141 for (int i=0; i<QUEUE_LENGTH; i++) { 142 mKeys[i] = null; 143 mQueue[i] = null; 144 } 145 mQueuePos = 0; 146 sendEmptyMessage(MSG_ADVANCE); 147 } 148 } 149 150 public void handleMessage(Message msg) { 151 switch (msg.what) { 152 case MSG_ADVANCE: 153 advance(); 154 break; 155 } 156 } 157 158 private void advance() { 159 // Out with the old... 160 if (mCurrentView != null) { 161 mWindow.removeView(mCurrentView); 162 mCurrentView = null; 163 mCurrentKey = null; 164 mCurrentNotification = null; 165 } 166 167 // In with the new... 168 dequeue(); 169 while (mCurrentNotification != null) { 170 mCurrentView = makeTickerView(mCurrentNotification); 171 if (mCurrentView != null) { 172 if (mWindow == null) { 173 mWindow = makeWindow(); 174 WindowManagerImpl.getDefault().addView(mWindow, mWindow.getLayoutParams()); 175 } 176 mWindow.addView(mCurrentView); 177 sendEmptyMessageDelayed(MSG_ADVANCE, ADVANCE_DELAY); 178 break; 179 } 180 dequeue(); 181 } 182 183 // if there's nothing left, close the window 184 mWindowShouldClose = (mCurrentView == null && mWindow != null); 185 } 186 187 private void dequeue() { 188 mCurrentKey = mKeys[0]; 189 mCurrentNotification = mQueue[0]; 190 if (false) { 191 Slog.d(TAG, "dequeue mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue)); 192 } 193 final int N = mQueuePos; 194 for (int i=0; i<N; i++) { 195 mKeys[i] = mKeys[i+1]; 196 mQueue[i] = mQueue[i+1]; 197 } 198 mKeys[N] = null; 199 mQueue[N] = null; 200 if (mQueuePos > 0) { 201 mQueuePos--; 202 } 203 } 204 205 private ViewGroup makeWindow() { 206 final Resources res = mContext.getResources(); 207 final FrameLayout view = new FrameLayout(mContext); 208 final int width = res.getDimensionPixelSize(R.dimen.notification_ticker_width); 209 final int height = res.getDimensionPixelSize( 210 android.R.dimen.notification_large_icon_height); 211 int windowFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 212 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 213 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 214 if (CLICKABLE_TICKER) { 215 windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 216 } else { 217 windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 218 } 219 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, 220 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, windowFlags, 221 PixelFormat.TRANSLUCENT); 222 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 223// lp.windowAnimations = com.android.internal.R.style.Animation_Toast; 224 225 mLayoutTransition = new LayoutTransition(); 226 mLayoutTransition.addTransitionListener(this); 227 view.setLayoutTransition(mLayoutTransition); 228 lp.setTitle("NotificationTicker"); 229 view.setLayoutParams(lp); 230 return view; 231 } 232 233 public void startTransition(LayoutTransition transition, ViewGroup container, 234 View view, int transitionType) {} 235 236 public void endTransition(LayoutTransition transition, ViewGroup container, 237 View view, int transitionType) { 238 if (mWindowShouldClose) { 239 WindowManagerImpl.getDefault().removeView(mWindow); 240 mWindow = null; 241 mWindowShouldClose = false; 242 mBar.doneTicking(); 243 } 244 } 245 246 private View makeTickerView(StatusBarNotification notification) { 247 final Notification n = notification.notification; 248 249 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 250 Context.LAYOUT_INFLATER_SERVICE); 251 252 ViewGroup group; 253 int layoutId; 254 int iconId; 255 if (n.largeIcon != null) { 256 iconId = R.id.right_icon; 257 } else { 258 iconId = R.id.left_icon; 259 } 260 if (n.tickerView != null) { 261 group = (ViewGroup)inflater.inflate(R.layout.status_bar_ticker_panel, null, false); 262 View expanded = null; 263 Exception exception = null; 264 try { 265 expanded = n.tickerView.apply(mContext, group); 266 } 267 catch (RuntimeException e) { 268 exception = e; 269 } 270 if (expanded == null) { 271 final String ident = notification.pkg 272 + "/0x" + Integer.toHexString(notification.id); 273 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 274 return null; 275 } 276 final int statusBarHeight = mContext.getResources().getDimensionPixelSize( 277 com.android.internal.R.dimen.status_bar_height); 278 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 279 ViewGroup.LayoutParams.WRAP_CONTENT, statusBarHeight, 1.0f); 280 lp.gravity = Gravity.BOTTOM; 281 group.addView(expanded, lp); 282 } else if (n.tickerText != null) { 283 group = (ViewGroup)inflater.inflate(R.layout.status_bar_ticker_compat, mWindow, false); 284 final Drawable icon = StatusBarIconView.getIcon(mContext, 285 new StatusBarIcon(notification.pkg, n.icon, n.iconLevel, 0)); 286 ImageView iv = (ImageView)group.findViewById(iconId); 287 iv.setImageDrawable(icon); 288 iv.setVisibility(View.VISIBLE); 289 TextView tv = (TextView)group.findViewById(R.id.text); 290 tv.setText(n.tickerText); 291 } else { 292 throw new RuntimeException("tickerView==null && tickerText==null"); 293 } 294 ImageView largeIcon = (ImageView)group.findViewById(R.id.large_icon); 295 if (n.largeIcon != null) { 296 largeIcon.setImageBitmap(n.largeIcon); 297 largeIcon.setVisibility(View.VISIBLE); 298 } 299 300 if (CLICKABLE_TICKER) { 301 PendingIntent contentIntent = notification.notification.contentIntent; 302 if (contentIntent != null) { 303 group.setOnClickListener(mBar.makeClicker(contentIntent, 304 notification.pkg, notification.tag, notification.id)); 305 } else { 306 group.setOnClickListener(null); 307 } 308 } 309 310 return group; 311 } 312} 313 314