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_NAVIGATION_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.system_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.system_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