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