BaseStatusBar.java revision bc5559f1922d72715940f754faffc82da4100eab
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;
18
19import java.util.ArrayList;
20
21import android.app.ActivityManagerNative;
22import android.app.KeyguardManager;
23import android.app.PendingIntent;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.graphics.Rect;
29import android.os.Build;
30import android.os.Handler;
31import android.os.IBinder;
32import android.os.Message;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.util.Log;
36import android.util.Slog;
37import android.view.Display;
38import android.view.IWindowManager;
39import android.view.LayoutInflater;
40import android.view.MotionEvent;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.ViewGroup.LayoutParams;
44import android.view.WindowManager;
45import android.view.WindowManagerImpl;
46import android.widget.LinearLayout;
47import android.widget.RemoteViews;
48
49import com.android.internal.statusbar.IStatusBarService;
50import com.android.internal.statusbar.StatusBarIcon;
51import com.android.internal.statusbar.StatusBarIconList;
52import com.android.internal.statusbar.StatusBarNotification;
53import com.android.internal.widget.SizeAdaptiveLayout;
54import com.android.systemui.SystemUI;
55import com.android.systemui.recent.RecentsPanelView;
56import com.android.systemui.recent.RecentTasksLoader;
57import com.android.systemui.recent.TaskDescription;
58import com.android.systemui.statusbar.CommandQueue;
59import com.android.systemui.statusbar.tablet.StatusBarPanel;
60
61import com.android.systemui.R;
62
63public abstract class BaseStatusBar extends SystemUI implements
64    CommandQueue.Callbacks, RecentsPanelView.OnRecentsPanelVisibilityChangedListener {
65    static final String TAG = "StatusBar";
66    private static final boolean DEBUG = false;
67
68    protected static final int MSG_OPEN_RECENTS_PANEL = 1020;
69    protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
70    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
71    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
72
73    protected CommandQueue mCommandQueue;
74    protected IStatusBarService mBarService;
75    protected H mHandler = createHandler();
76
77    // used to notify status bar for suppressing notification LED
78    protected boolean mPanelSlightlyVisible;
79
80    // Recent apps
81    protected RecentsPanelView mRecentsPanel;
82    protected RecentTasksLoader mRecentTasksLoader;
83
84    // UI-specific methods
85
86    /**
87     * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
88     * and add them to the window manager.
89     */
90    protected abstract void createAndAddWindows();
91
92    protected Display mDisplay;
93    private IWindowManager mWindowManager;
94
95
96    public IWindowManager getWindowManager() {
97        return mWindowManager;
98    }
99
100    public Display getDisplay() {
101        return mDisplay;
102    }
103
104    public IStatusBarService getStatusBarService() {
105        return mBarService;
106    }
107
108    public void start() {
109        mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
110                .getDefaultDisplay();
111
112        mWindowManager = IWindowManager.Stub.asInterface(
113                ServiceManager.getService(Context.WINDOW_SERVICE));
114
115        mBarService = IStatusBarService.Stub.asInterface(
116                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
117
118        // Connect in to the status bar manager service
119        StatusBarIconList iconList = new StatusBarIconList();
120        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
121        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
122        mCommandQueue = new CommandQueue(this, iconList);
123
124        int[] switches = new int[7];
125        ArrayList<IBinder> binders = new ArrayList<IBinder>();
126        try {
127            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
128                    switches, binders);
129        } catch (RemoteException ex) {
130            // If the system process isn't there we're doomed anyway.
131        }
132
133        createAndAddWindows();
134
135        disable(switches[0]);
136        setSystemUiVisibility(switches[1], 0xffffffff);
137        topAppWindowChanged(switches[2] != 0);
138        // StatusBarManagerService has a back up of IME token and it's restored here.
139        setImeWindowStatus(binders.get(0), switches[3], switches[4]);
140        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
141
142        // Set up the initial icon state
143        int N = iconList.size();
144        int viewIndex = 0;
145        for (int i=0; i<N; i++) {
146            StatusBarIcon icon = iconList.getIcon(i);
147            if (icon != null) {
148                addIcon(iconList.getSlot(i), i, viewIndex, icon);
149                viewIndex++;
150            }
151        }
152
153        // Set up the initial notification state
154        N = notificationKeys.size();
155        if (N == notifications.size()) {
156            for (int i=0; i<N; i++) {
157                addNotification(notificationKeys.get(i), notifications.get(i));
158            }
159        } else {
160            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
161                    + " notifications=" + notifications.size());
162        }
163
164        if (DEBUG) {
165            Slog.d(TAG, String.format(
166                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
167                   iconList.size(),
168                   switches[0],
169                   switches[1],
170                   switches[2],
171                   switches[3]
172                   ));
173        }
174    }
175
176    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
177        View vetoButton = row.findViewById(R.id.veto);
178        if (n.isClearable()) {
179            final String _pkg = n.pkg;
180            final String _tag = n.tag;
181            final int _id = n.id;
182            vetoButton.setOnClickListener(new View.OnClickListener() {
183                    public void onClick(View v) {
184                        try {
185                            mBarService.onNotificationClear(_pkg, _tag, _id);
186                        } catch (RemoteException ex) {
187                            // system process is dead if we're here.
188                        }
189                    }
190                });
191            vetoButton.setVisibility(View.VISIBLE);
192        } else {
193            vetoButton.setVisibility(View.GONE);
194        }
195        return vetoButton;
196    }
197
198
199    protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
200        if (sbn.notification.contentView.getLayoutId() !=
201                com.android.internal.R.layout.notification_template_base) {
202            int version = 0;
203            try {
204                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0);
205                version = info.targetSdkVersion;
206            } catch (NameNotFoundException ex) {
207                Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex);
208            }
209            if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
210                content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
211            } else {
212                content.setBackgroundResource(R.drawable.notification_row_bg);
213            }
214        }
215    }
216
217    public void dismissIntruder() {
218        // pass
219    }
220
221    @Override
222    public void toggleRecentApps() {
223        int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
224            ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
225        mHandler.removeMessages(msg);
226        mHandler.sendEmptyMessage(msg);
227    }
228
229    @Override
230    public void preloadRecentApps() {
231        int msg = MSG_PRELOAD_RECENT_APPS;
232        mHandler.removeMessages(msg);
233        mHandler.sendEmptyMessage(msg);
234    }
235
236    @Override
237    public void cancelPreloadRecentApps() {
238        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
239        mHandler.removeMessages(msg);
240        mHandler.sendEmptyMessage(msg);
241    }
242
243    @Override
244    public void onRecentsPanelVisibilityChanged(boolean visible) {
245    }
246
247    protected abstract WindowManager.LayoutParams getRecentsLayoutParams(
248            LayoutParams layoutParams);
249
250    protected void updateRecentsPanel() {
251        // Recents Panel
252        boolean visible = false;
253        ArrayList<TaskDescription> recentTasksList = null;
254        boolean firstScreenful = false;
255        if (mRecentsPanel != null) {
256            visible = mRecentsPanel.isShowing();
257            WindowManagerImpl.getDefault().removeView(mRecentsPanel);
258            if (visible) {
259                recentTasksList = mRecentsPanel.getRecentTasksList();
260                firstScreenful = mRecentsPanel.getFirstScreenful();
261            }
262        }
263
264        // Provide RecentsPanelView with a temporary parent to allow layout params to work.
265        LinearLayout tmpRoot = new LinearLayout(mContext);
266        mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate(
267                 R.layout.status_bar_recent_panel, tmpRoot, false);
268        mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader);
269        mRecentTasksLoader.setRecentsPanel(mRecentsPanel);
270        mRecentsPanel.setOnTouchListener(
271                 new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, mRecentsPanel));
272        mRecentsPanel.setVisibility(View.GONE);
273
274
275        WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams());
276
277        WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
278        mRecentsPanel.setBar(this);
279        if (visible) {
280            mRecentsPanel.show(true, false, recentTasksList, firstScreenful);
281        }
282
283    }
284
285    protected H createHandler() {
286         return new H();
287    }
288
289    protected class H extends Handler {
290        public void handleMessage(Message m) {
291            switch (m.what) {
292             case MSG_OPEN_RECENTS_PANEL:
293                  if (DEBUG) Slog.d(TAG, "opening recents panel");
294                  if (mRecentsPanel != null) {
295                      mRecentsPanel.show(true, true);
296                  }
297                  break;
298             case MSG_CLOSE_RECENTS_PANEL:
299                  if (DEBUG) Slog.d(TAG, "closing recents panel");
300                  if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
301                      mRecentsPanel.show(false, true);
302                  }
303                  break;
304             case MSG_PRELOAD_RECENT_APPS:
305                  if (DEBUG) Slog.d(TAG, "preloading recents");
306                  mRecentsPanel.preloadRecentTasksList();
307                  break;
308             case MSG_CANCEL_PRELOAD_RECENT_APPS:
309                  if (DEBUG) Slog.d(TAG, "cancel preloading recents");
310                  mRecentsPanel.clearRecentTasksList();
311                  break;
312            }
313        }
314    }
315
316    public class TouchOutsideListener implements View.OnTouchListener {
317        private int mMsg;
318        private StatusBarPanel mPanel;
319
320        public TouchOutsideListener(int msg, StatusBarPanel panel) {
321            mMsg = msg;
322            mPanel = panel;
323        }
324
325        public boolean onTouch(View v, MotionEvent ev) {
326            final int action = ev.getAction();
327            if (action == MotionEvent.ACTION_OUTSIDE
328                || (action == MotionEvent.ACTION_DOWN
329                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
330                mHandler.removeMessages(mMsg);
331                mHandler.sendEmptyMessage(mMsg);
332                return true;
333            }
334            return false;
335        }
336    }
337
338    protected void workAroundBadLayerDrawableOpacity(View v) {
339    }
340
341    protected  boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
342        int minHeight =
343                mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
344        int maxHeight =
345                mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
346        StatusBarNotification sbn = entry.notification;
347        RemoteViews oneU = sbn.notification.contentView;
348        RemoteViews large = sbn.notification.bigContentView;
349        if (oneU == null) {
350            return false;
351        }
352
353        // create the row view
354        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
355                Context.LAYOUT_INFLATER_SERVICE);
356        View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
357
358        // XXX: temporary: while testing big notifications, auto-expand all of them
359        ViewGroup.LayoutParams lp = row.getLayoutParams();
360        if (large != null) {
361            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
362        } else {
363            lp.height = minHeight;
364        }
365        row.setLayoutParams(lp);
366        workAroundBadLayerDrawableOpacity(row);
367        View vetoButton = updateNotificationVetoButton(row, sbn);
368        vetoButton.setContentDescription(mContext.getString(
369                R.string.accessibility_remove_notification));
370
371        // NB: the large icon is now handled entirely by the template
372
373        // bind the click event to the content area
374        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
375        ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
376
377        // Ensure that R.id.content is properly set to 64dp high if 1U
378        lp = content.getLayoutParams();
379        if (large == null) {
380            lp.height = minHeight;
381        }
382        content.setLayoutParams(lp);
383
384        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
385
386        PendingIntent contentIntent = sbn.notification.contentIntent;
387        if (contentIntent != null) {
388            final View.OnClickListener listener = new NotificationClicker(contentIntent,
389                    sbn.pkg, sbn.tag, sbn.id);
390            content.setOnClickListener(listener);
391        } else {
392            content.setOnClickListener(null);
393        }
394
395        View expandedOneU = null;
396        View expandedLarge = null;
397        Exception exception = null;
398        try {
399            expandedOneU = oneU.apply(mContext, adaptive);
400            if (large != null) {
401                expandedLarge = large.apply(mContext, adaptive);
402            }
403        }
404        catch (RuntimeException e) {
405            exception = e;
406        }
407        if (expandedOneU == null && expandedLarge == null) {
408            final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
409            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
410            return false;
411        } else {
412            if (expandedOneU != null) {
413                SizeAdaptiveLayout.LayoutParams params =
414                        new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams());
415                params.minHeight = minHeight;
416                params.maxHeight = minHeight;
417                adaptive.addView(expandedOneU, params);
418            }
419            if (expandedLarge != null) {
420                SizeAdaptiveLayout.LayoutParams params =
421                        new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams());
422                params.minHeight = minHeight+1;
423                params.maxHeight = SizeAdaptiveLayout.LayoutParams.UNBOUNDED;
424                adaptive.addView(expandedLarge, params);
425            }
426            row.setDrawingCacheEnabled(true);
427        }
428
429        applyLegacyRowBackground(sbn, content);
430
431        entry.row = row;
432        entry.content = content;
433        entry.expanded = expandedOneU;
434        entry.expandedLarge = expandedOneU;
435
436        return true;
437    }
438
439    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
440        return new NotificationClicker(intent, pkg, tag, id);
441    }
442
443    private class NotificationClicker implements View.OnClickListener {
444        private PendingIntent mIntent;
445        private String mPkg;
446        private String mTag;
447        private int mId;
448
449        NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
450            mIntent = intent;
451            mPkg = pkg;
452            mTag = tag;
453            mId = id;
454        }
455
456        public void onClick(View v) {
457            try {
458                // The intent we are sending is for the application, which
459                // won't have permission to immediately start an activity after
460                // the user switches to home.  We know it is safe to do at this
461                // point, so make sure new activity switches are now allowed.
462                ActivityManagerNative.getDefault().resumeAppSwitches();
463                // Also, notifications can be launched from the lock screen,
464                // so dismiss the lock screen when the activity starts.
465                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
466            } catch (RemoteException e) {
467            }
468
469            if (mIntent != null) {
470                int[] pos = new int[2];
471                v.getLocationOnScreen(pos);
472                Intent overlay = new Intent();
473                overlay.setSourceBounds(
474                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
475                try {
476                    mIntent.send(mContext, 0, overlay);
477                } catch (PendingIntent.CanceledException e) {
478                    // the stack trace isn't very helpful here.  Just log the exception message.
479                    Slog.w(TAG, "Sending contentIntent failed: " + e);
480                }
481
482                KeyguardManager kgm =
483                    (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
484                if (kgm != null) kgm.exitKeyguardSecurely(null);
485            }
486
487            try {
488                mBarService.onNotificationClick(mPkg, mTag, mId);
489            } catch (RemoteException ex) {
490                // system process is dead if we're here.
491            }
492
493            // close the shade if it was open
494            animateCollapse();
495            visibilityChanged(false);
496
497            // If this click was on the intruder alert, hide that instead
498//            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
499        }
500    }
501    /**
502     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
503     * This was added last-minute and is inconsistent with the way the rest of the notifications
504     * are handled, because the notification isn't really cancelled.  The lights are just
505     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
506     * this is what he wants. (see bug 1131461)
507     */
508    protected void visibilityChanged(boolean visible) {
509        if (mPanelSlightlyVisible != visible) {
510            mPanelSlightlyVisible = visible;
511            try {
512                mBarService.onPanelRevealed();
513            } catch (RemoteException ex) {
514                // Won't fail unless the world has ended.
515            }
516        }
517    }
518
519}
520