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