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