| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.statusbar.tablet; |
| |
| import java.util.Arrays; |
| |
| import android.animation.LayoutTransition; |
| import android.app.Notification; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.PixelFormat; |
| import android.graphics.drawable.Drawable; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.util.Slog; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.WindowManager; |
| import android.view.WindowManagerImpl; |
| import android.widget.FrameLayout; |
| import android.widget.ImageView; |
| import android.widget.FrameLayout; |
| import android.widget.TextView; |
| |
| import com.android.internal.statusbar.StatusBarIcon; |
| import com.android.internal.statusbar.StatusBarNotification; |
| |
| import com.android.systemui.R; |
| import com.android.systemui.statusbar.StatusBarIconView; |
| |
| public class TabletTicker |
| extends Handler |
| implements LayoutTransition.TransitionListener { |
| |
| private static final String TAG = "StatusBar.TabletTicker"; |
| |
| private static final boolean CLICKABLE_TICKER = true; |
| |
| // 3 is enough to let us see most cases, but not get so far behind that it's too annoying. |
| private static final int QUEUE_LENGTH = 3; |
| |
| private static final int MSG_ADVANCE = 1; |
| |
| private static final int ADVANCE_DELAY = 5000; // 5 seconds |
| |
| private Context mContext; |
| |
| private ViewGroup mWindow; |
| private IBinder mCurrentKey; |
| private StatusBarNotification mCurrentNotification; |
| private View mCurrentView; |
| |
| private IBinder[] mKeys = new IBinder[QUEUE_LENGTH]; |
| private StatusBarNotification[] mQueue = new StatusBarNotification[QUEUE_LENGTH]; |
| private int mQueuePos; |
| |
| private TabletStatusBar mBar; |
| |
| private LayoutTransition mLayoutTransition; |
| private boolean mWindowShouldClose; |
| |
| public TabletTicker(TabletStatusBar bar) { |
| mBar = bar; |
| mContext = bar.getContext(); |
| } |
| |
| public void add(IBinder key, StatusBarNotification notification) { |
| if (false) { |
| Slog.d(TAG, "add 1 mCurrentNotification=" + mCurrentNotification |
| + " mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue)); |
| } |
| |
| // If it's already in here, remove whatever's in there and put the new one at the end. |
| remove(key, false); |
| |
| mKeys[mQueuePos] = key; |
| mQueue[mQueuePos] = notification; |
| |
| // If nothing is running now, start the next one. |
| if (mQueuePos == 0 && mCurrentNotification == null) { |
| sendEmptyMessage(MSG_ADVANCE); |
| } |
| |
| if (mQueuePos < QUEUE_LENGTH - 1) { |
| mQueuePos++; |
| } |
| } |
| |
| public void remove(IBinder key) { |
| remove(key, true); |
| } |
| |
| public void remove(IBinder key, boolean advance) { |
| if (mCurrentKey == key) { |
| // Showing now |
| if (advance) { |
| removeMessages(MSG_ADVANCE); |
| sendEmptyMessage(MSG_ADVANCE); |
| } |
| } else { |
| // In the queue |
| for (int i=0; i<QUEUE_LENGTH; i++) { |
| if (mKeys[i] == key) { |
| for (; i<QUEUE_LENGTH-1; i++) { |
| mKeys[i] = mKeys[i+1]; |
| mQueue[i] = mQueue[i+1]; |
| } |
| mKeys[QUEUE_LENGTH-1] = null; |
| mQueue[QUEUE_LENGTH-1] = null; |
| if (mQueuePos > 0) { |
| mQueuePos--; |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| public void halt() { |
| removeMessages(MSG_ADVANCE); |
| if (mCurrentView != null || mQueuePos != 0) { |
| for (int i=0; i<QUEUE_LENGTH; i++) { |
| mKeys[i] = null; |
| mQueue[i] = null; |
| } |
| mQueuePos = 0; |
| sendEmptyMessage(MSG_ADVANCE); |
| } |
| } |
| |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_ADVANCE: |
| advance(); |
| break; |
| } |
| } |
| |
| private void advance() { |
| // Out with the old... |
| if (mCurrentView != null) { |
| mWindow.removeView(mCurrentView); |
| mCurrentView = null; |
| mCurrentKey = null; |
| mCurrentNotification = null; |
| } |
| |
| // In with the new... |
| dequeue(); |
| while (mCurrentNotification != null) { |
| mCurrentView = makeTickerView(mCurrentNotification); |
| if (mCurrentView != null) { |
| if (mWindow == null) { |
| mWindow = makeWindow(); |
| WindowManagerImpl.getDefault().addView(mWindow, mWindow.getLayoutParams()); |
| } |
| |
| mWindow.addView(mCurrentView); |
| sendEmptyMessageDelayed(MSG_ADVANCE, ADVANCE_DELAY); |
| break; |
| } |
| dequeue(); |
| } |
| |
| // if there's nothing left, close the window |
| mWindowShouldClose = (mCurrentView == null && mWindow != null); |
| } |
| |
| private void dequeue() { |
| mCurrentKey = mKeys[0]; |
| mCurrentNotification = mQueue[0]; |
| if (false) { |
| Slog.d(TAG, "dequeue mQueuePos=" + mQueuePos + " mQueue=" + Arrays.toString(mQueue)); |
| } |
| final int N = mQueuePos; |
| for (int i=0; i<N; i++) { |
| mKeys[i] = mKeys[i+1]; |
| mQueue[i] = mQueue[i+1]; |
| } |
| mKeys[N] = null; |
| mQueue[N] = null; |
| if (mQueuePos > 0) { |
| mQueuePos--; |
| } |
| } |
| |
| private ViewGroup makeWindow() { |
| final Resources res = mContext.getResources(); |
| final FrameLayout view = new FrameLayout(mContext); |
| final int width = res.getDimensionPixelSize(R.dimen.notification_ticker_width); |
| final int height = res.getDimensionPixelSize( |
| android.R.dimen.notification_large_icon_height); |
| int windowFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
| | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; |
| if (CLICKABLE_TICKER) { |
| windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; |
| } else { |
| windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; |
| } |
| WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, |
| WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, windowFlags, |
| PixelFormat.TRANSLUCENT); |
| lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; |
| // lp.windowAnimations = com.android.internal.R.style.Animation_Toast; |
| |
| mLayoutTransition = new LayoutTransition(); |
| mLayoutTransition.addTransitionListener(this); |
| view.setLayoutTransition(mLayoutTransition); |
| lp.setTitle("NotificationTicker"); |
| view.setLayoutParams(lp); |
| return view; |
| } |
| |
| public void startTransition(LayoutTransition transition, ViewGroup container, |
| View view, int transitionType) {} |
| |
| public void endTransition(LayoutTransition transition, ViewGroup container, |
| View view, int transitionType) { |
| if (mWindowShouldClose) { |
| WindowManagerImpl.getDefault().removeView(mWindow); |
| mWindow = null; |
| mWindowShouldClose = false; |
| mBar.doneTicking(); |
| } |
| } |
| |
| private View makeTickerView(StatusBarNotification notification) { |
| final Notification n = notification.notification; |
| |
| LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| |
| ViewGroup group; |
| int layoutId; |
| int iconId; |
| if (n.largeIcon != null) { |
| iconId = R.id.right_icon; |
| } else { |
| iconId = R.id.left_icon; |
| } |
| if (n.tickerView != null) { |
| group = (ViewGroup)inflater.inflate(R.layout.status_bar_ticker_panel, null, false); |
| ViewGroup content = (FrameLayout) group.findViewById(R.id.ticker_expanded); |
| View expanded = null; |
| Exception exception = null; |
| try { |
| expanded = n.tickerView.apply(mContext, content); |
| } |
| catch (RuntimeException e) { |
| exception = e; |
| } |
| if (expanded == null) { |
| final String ident = notification.pkg |
| + "/0x" + Integer.toHexString(notification.id); |
| Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); |
| return null; |
| } |
| FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( |
| ViewGroup.LayoutParams.MATCH_PARENT, |
| ViewGroup.LayoutParams.MATCH_PARENT); |
| content.addView(expanded, lp); |
| } else if (n.tickerText != null) { |
| group = (ViewGroup)inflater.inflate(R.layout.status_bar_ticker_compat, mWindow, false); |
| final Drawable icon = StatusBarIconView.getIcon(mContext, |
| new StatusBarIcon(notification.pkg, n.icon, n.iconLevel, 0)); |
| ImageView iv = (ImageView)group.findViewById(iconId); |
| iv.setImageDrawable(icon); |
| iv.setVisibility(View.VISIBLE); |
| TextView tv = (TextView)group.findViewById(R.id.text); |
| tv.setText(n.tickerText); |
| } else { |
| throw new RuntimeException("tickerView==null && tickerText==null"); |
| } |
| ImageView largeIcon = (ImageView)group.findViewById(R.id.large_icon); |
| if (n.largeIcon != null) { |
| largeIcon.setImageBitmap(n.largeIcon); |
| largeIcon.setVisibility(View.VISIBLE); |
| } |
| |
| if (CLICKABLE_TICKER) { |
| PendingIntent contentIntent = notification.notification.contentIntent; |
| if (contentIntent != null) { |
| // create the usual notification clicker, but chain it together with a halt() call |
| // to abort the ticker too |
| final View.OnClickListener clicker = mBar.makeClicker(contentIntent, |
| notification.pkg, notification.tag, notification.id); |
| group.setOnClickListener(new View.OnClickListener() { |
| public void onClick(View v) { |
| halt(); |
| clicker.onClick(v); |
| } |
| }); |
| } else { |
| group.setOnClickListener(null); |
| } |
| } |
| |
| return group; |
| } |
| } |
| |