• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import java.util.ArrayList;
20 
21 import android.app.ActivityManagerNative;
22 import android.app.KeyguardManager;
23 import android.app.Notification;
24 import android.app.PendingIntent;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.database.ContentObserver;
30 import android.graphics.Rect;
31 import android.net.Uri;
32 import android.os.Build;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.os.Message;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.provider.Settings;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.util.Slog;
42 import android.view.Display;
43 import android.view.IWindowManager;
44 import android.view.LayoutInflater;
45 import android.view.MenuItem;
46 import android.view.MotionEvent;
47 import android.view.View;
48 import android.view.ViewGroup;
49 import android.view.ViewGroup.LayoutParams;
50 import android.view.WindowManager;
51 import android.view.WindowManagerImpl;
52 import android.widget.ImageView;
53 import android.widget.LinearLayout;
54 import android.widget.RemoteViews;
55 import android.widget.PopupMenu;
56 
57 import com.android.internal.statusbar.IStatusBarService;
58 import com.android.internal.statusbar.StatusBarIcon;
59 import com.android.internal.statusbar.StatusBarIconList;
60 import com.android.internal.statusbar.StatusBarNotification;
61 import com.android.internal.widget.SizeAdaptiveLayout;
62 import com.android.systemui.SearchPanelView;
63 import com.android.systemui.SystemUI;
64 import com.android.systemui.recent.RecentsPanelView;
65 import com.android.systemui.recent.RecentTasksLoader;
66 import com.android.systemui.recent.TaskDescription;
67 import com.android.systemui.statusbar.CommandQueue;
68 import com.android.systemui.statusbar.NotificationData.Entry;
69 import com.android.systemui.statusbar.policy.NotificationRowLayout;
70 import com.android.systemui.statusbar.tablet.StatusBarPanel;
71 
72 import com.android.systemui.R;
73 
74 public abstract class BaseStatusBar extends SystemUI implements
75     CommandQueue.Callbacks, RecentsPanelView.OnRecentsPanelVisibilityChangedListener {
76     static final String TAG = "StatusBar";
77     private static final boolean DEBUG = false;
78 
79     protected static final int MSG_OPEN_RECENTS_PANEL = 1020;
80     protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
81     protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
82     protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
83     protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
84     protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
85     protected static final int MSG_SHOW_INTRUDER = 1026;
86     protected static final int MSG_HIDE_INTRUDER = 1027;
87 
88     protected static final boolean ENABLE_INTRUDERS = false;
89 
90     // Should match the value in PhoneWindowManager
91     public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
92 
93     public static final int EXPANDED_LEAVE_ALONE = -10000;
94     public static final int EXPANDED_FULL_OPEN = -10001;
95 
96     protected CommandQueue mCommandQueue;
97     protected IStatusBarService mBarService;
98     protected H mHandler = createHandler();
99 
100     // all notifications
101     protected NotificationData mNotificationData = new NotificationData();
102     protected NotificationRowLayout mPile;
103 
104     protected StatusBarNotification mCurrentlyIntrudingNotification;
105 
106     // used to notify status bar for suppressing notification LED
107     protected boolean mPanelSlightlyVisible;
108 
109     // Search panel
110     protected SearchPanelView mSearchPanelView;
111 
112     // Recent apps
113     protected RecentsPanelView mRecentsPanel;
114     protected RecentTasksLoader mRecentTasksLoader;
115 
116     protected PopupMenu mNotificationBlamePopup;
117 
118     // UI-specific methods
119 
120     /**
121      * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
122      * and add them to the window manager.
123      */
createAndAddWindows()124     protected abstract void createAndAddWindows();
125 
126     protected Display mDisplay;
127     private IWindowManager mWindowManager;
128     private boolean mDeviceProvisioned = false;
129 
getWindowManager()130     public IWindowManager getWindowManager() {
131         return mWindowManager;
132     }
133 
getDisplay()134     public Display getDisplay() {
135         return mDisplay;
136     }
137 
getStatusBarService()138     public IStatusBarService getStatusBarService() {
139         return mBarService;
140     }
141 
isDeviceProvisioned()142     protected boolean isDeviceProvisioned() {
143         return mDeviceProvisioned;
144     }
145 
146     private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
147         @Override
148         public void onChange(boolean selfChange) {
149             final boolean provisioned = 0 != Settings.Secure.getInt(
150                     mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0);
151             if (provisioned != mDeviceProvisioned) {
152                 mDeviceProvisioned = provisioned;
153                 updateNotificationIcons();
154             }
155         }
156     };
157 
158     private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
159         @Override
160         public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
161             final boolean isActivity = pendingIntent.isActivity();
162             if (isActivity) {
163                 try {
164                     // The intent we are sending is for the application, which
165                     // won't have permission to immediately start an activity after
166                     // the user switches to home.  We know it is safe to do at this
167                     // point, so make sure new activity switches are now allowed.
168                     ActivityManagerNative.getDefault().resumeAppSwitches();
169                     // Also, notifications can be launched from the lock screen,
170                     // so dismiss the lock screen when the activity starts.
171                     ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
172                 } catch (RemoteException e) {
173                 }
174             }
175 
176             boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
177 
178             if (isActivity && handled) {
179                 // close the shade if it was open
180                 animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE);
181                 visibilityChanged(false);
182             }
183             return handled;
184         }
185     };
186 
start()187     public void start() {
188         mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
189                 .getDefaultDisplay();
190 
191         mProvisioningObserver.onChange(false); // set up
192         mContext.getContentResolver().registerContentObserver(
193                 Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED), true,
194                 mProvisioningObserver);
195 
196         mWindowManager = IWindowManager.Stub.asInterface(
197                 ServiceManager.getService(Context.WINDOW_SERVICE));
198 
199         mBarService = IStatusBarService.Stub.asInterface(
200                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
201 
202         // Connect in to the status bar manager service
203         StatusBarIconList iconList = new StatusBarIconList();
204         ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
205         ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
206         mCommandQueue = new CommandQueue(this, iconList);
207 
208         int[] switches = new int[7];
209         ArrayList<IBinder> binders = new ArrayList<IBinder>();
210         try {
211             mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
212                     switches, binders);
213         } catch (RemoteException ex) {
214             // If the system process isn't there we're doomed anyway.
215         }
216 
217         createAndAddWindows();
218 
219         disable(switches[0]);
220         setSystemUiVisibility(switches[1], 0xffffffff);
221         topAppWindowChanged(switches[2] != 0);
222         // StatusBarManagerService has a back up of IME token and it's restored here.
223         setImeWindowStatus(binders.get(0), switches[3], switches[4]);
224         setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
225 
226         // Set up the initial icon state
227         int N = iconList.size();
228         int viewIndex = 0;
229         for (int i=0; i<N; i++) {
230             StatusBarIcon icon = iconList.getIcon(i);
231             if (icon != null) {
232                 addIcon(iconList.getSlot(i), i, viewIndex, icon);
233                 viewIndex++;
234             }
235         }
236 
237         // Set up the initial notification state
238         N = notificationKeys.size();
239         if (N == notifications.size()) {
240             for (int i=0; i<N; i++) {
241                 addNotification(notificationKeys.get(i), notifications.get(i));
242             }
243         } else {
244             Log.wtf(TAG, "Notification list length mismatch: keys=" + N
245                     + " notifications=" + notifications.size());
246         }
247 
248         if (DEBUG) {
249             Slog.d(TAG, String.format(
250                     "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
251                    iconList.size(),
252                    switches[0],
253                    switches[1],
254                    switches[2],
255                    switches[3]
256                    ));
257         }
258     }
259 
updateNotificationVetoButton(View row, StatusBarNotification n)260     protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
261         View vetoButton = row.findViewById(R.id.veto);
262         if (n.isClearable()) {
263             final String _pkg = n.pkg;
264             final String _tag = n.tag;
265             final int _id = n.id;
266             vetoButton.setOnClickListener(new View.OnClickListener() {
267                     public void onClick(View v) {
268                         try {
269                             mBarService.onNotificationClear(_pkg, _tag, _id);
270                         } catch (RemoteException ex) {
271                             // system process is dead if we're here.
272                         }
273                     }
274                 });
275             vetoButton.setVisibility(View.VISIBLE);
276         } else {
277             vetoButton.setVisibility(View.GONE);
278         }
279         return vetoButton;
280     }
281 
282 
applyLegacyRowBackground(StatusBarNotification sbn, View content)283     protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
284         if (sbn.notification.contentView.getLayoutId() !=
285                 com.android.internal.R.layout.notification_template_base) {
286             int version = 0;
287             try {
288                 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0);
289                 version = info.targetSdkVersion;
290             } catch (NameNotFoundException ex) {
291                 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex);
292             }
293             if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
294                 content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
295             } else {
296                 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
297             }
298         }
299     }
300 
startApplicationDetailsActivity(String packageName)301     private void startApplicationDetailsActivity(String packageName) {
302         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
303                 Uri.fromParts("package", packageName, null));
304         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
305         mContext.startActivity(intent);
306     }
307 
getNotificationLongClicker()308     protected View.OnLongClickListener getNotificationLongClicker() {
309         return new View.OnLongClickListener() {
310             @Override
311             public boolean onLongClick(View v) {
312                 final String packageNameF = (String) v.getTag();
313                 if (packageNameF == null) return false;
314                 if (v.getWindowToken() == null) return false;
315                 mNotificationBlamePopup = new PopupMenu(mContext, v);
316                 mNotificationBlamePopup.getMenuInflater().inflate(
317                         R.menu.notification_popup_menu,
318                         mNotificationBlamePopup.getMenu());
319                 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
320                     public boolean onMenuItemClick(MenuItem item) {
321                         if (item.getItemId() == R.id.notification_inspect_item) {
322                             startApplicationDetailsActivity(packageNameF);
323                             animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE);
324                         } else {
325                             return false;
326                         }
327                         return true;
328                     }
329                 });
330                 mNotificationBlamePopup.show();
331 
332                 return true;
333             }
334         };
335     }
336 
337     public void dismissPopups() {
338         if (mNotificationBlamePopup != null) {
339             mNotificationBlamePopup.dismiss();
340             mNotificationBlamePopup = null;
341         }
342     }
343 
344     public void dismissIntruder() {
345         // pass
346     }
347 
348     @Override
349     public void toggleRecentApps() {
350         int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
351             ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
352         mHandler.removeMessages(msg);
353         mHandler.sendEmptyMessage(msg);
354     }
355 
356     @Override
357     public void preloadRecentApps() {
358         int msg = MSG_PRELOAD_RECENT_APPS;
359         mHandler.removeMessages(msg);
360         mHandler.sendEmptyMessage(msg);
361     }
362 
363     @Override
364     public void cancelPreloadRecentApps() {
365         int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
366         mHandler.removeMessages(msg);
367         mHandler.sendEmptyMessage(msg);
368     }
369 
370     @Override
371     public void showSearchPanel() {
372         int msg = MSG_OPEN_SEARCH_PANEL;
373         mHandler.removeMessages(msg);
374         mHandler.sendEmptyMessage(msg);
375     }
376 
377     @Override
378     public void hideSearchPanel() {
379         int msg = MSG_CLOSE_SEARCH_PANEL;
380         mHandler.removeMessages(msg);
381         mHandler.sendEmptyMessage(msg);
382     }
383 
384     @Override
385     public void onRecentsPanelVisibilityChanged(boolean visible) {
386     }
387 
388     protected abstract WindowManager.LayoutParams getRecentsLayoutParams(
389             LayoutParams layoutParams);
390 
391     protected abstract WindowManager.LayoutParams getSearchLayoutParams(
392             LayoutParams layoutParams);
393 
394     protected void updateRecentsPanel(int recentsResId) {
395         // Recents Panel
396         boolean visible = false;
397         ArrayList<TaskDescription> recentTasksList = null;
398         boolean firstScreenful = false;
399         if (mRecentsPanel != null) {
400             visible = mRecentsPanel.isShowing();
401             WindowManagerImpl.getDefault().removeView(mRecentsPanel);
402             if (visible) {
403                 recentTasksList = mRecentsPanel.getRecentTasksList();
404                 firstScreenful = mRecentsPanel.getFirstScreenful();
405             }
406         }
407 
408         // Provide RecentsPanelView with a temporary parent to allow layout params to work.
409         LinearLayout tmpRoot = new LinearLayout(mContext);
410         mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate(
411                 recentsResId, tmpRoot, false);
412         mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader);
413         mRecentTasksLoader.setRecentsPanel(mRecentsPanel);
414         mRecentsPanel.setOnTouchListener(
415                  new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, mRecentsPanel));
416         mRecentsPanel.setVisibility(View.GONE);
417 
418 
419         WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams());
420 
421         WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
422         mRecentsPanel.setBar(this);
423         if (visible) {
424             mRecentsPanel.show(true, false, recentTasksList, firstScreenful);
425         }
426 
427     }
428 
429     protected void updateSearchPanel() {
430         // Search Panel
431         boolean visible = false;
432         if (mSearchPanelView != null) {
433             visible = mSearchPanelView.isShowing();
434             WindowManagerImpl.getDefault().removeView(mSearchPanelView);
435         }
436 
437         // Provide SearchPanel with a temporary parent to allow layout params to work.
438         LinearLayout tmpRoot = new LinearLayout(mContext);
439         mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
440                  R.layout.status_bar_search_panel, tmpRoot, false);
441         mSearchPanelView.setOnTouchListener(
442                  new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
443         mSearchPanelView.setVisibility(View.GONE);
444 
445         WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
446 
447         WindowManagerImpl.getDefault().addView(mSearchPanelView, lp);
448         mSearchPanelView.setBar(this);
449         if (visible) {
450             mSearchPanelView.show(true, false);
451         }
452     }
453 
454     protected H createHandler() {
455          return new H();
456     }
457 
458     static void sendCloseSystemWindows(Context context, String reason) {
459         if (ActivityManagerNative.isSystemReady()) {
460             try {
461                 ActivityManagerNative.getDefault().closeSystemDialogs(reason);
462             } catch (RemoteException e) {
463             }
464         }
465     }
466 
467     protected class H extends Handler {
468         public void handleMessage(Message m) {
469             switch (m.what) {
470              case MSG_OPEN_RECENTS_PANEL:
471                   if (DEBUG) Slog.d(TAG, "opening recents panel");
472                   if (mRecentsPanel != null) {
473                       mRecentsPanel.show(true, false);
474                   }
475                   break;
476              case MSG_CLOSE_RECENTS_PANEL:
477                   if (DEBUG) Slog.d(TAG, "closing recents panel");
478                   if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
479                       mRecentsPanel.show(false, false);
480                   }
481                   break;
482              case MSG_PRELOAD_RECENT_APPS:
483                   if (DEBUG) Slog.d(TAG, "preloading recents");
484                   mRecentsPanel.preloadRecentTasksList();
485                   break;
486              case MSG_CANCEL_PRELOAD_RECENT_APPS:
487                   if (DEBUG) Slog.d(TAG, "cancel preloading recents");
488                   mRecentsPanel.clearRecentTasksList();
489                   break;
490              case MSG_OPEN_SEARCH_PANEL:
491                  if (DEBUG) Slog.d(TAG, "opening search panel");
492                  if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
493                      mSearchPanelView.show(true, true);
494                  }
495                  break;
496              case MSG_CLOSE_SEARCH_PANEL:
497                  if (DEBUG) Slog.d(TAG, "closing search panel");
498                  if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
499                      mSearchPanelView.show(false, true);
500                  }
501                  break;
502             }
503         }
504     }
505 
506     public class TouchOutsideListener implements View.OnTouchListener {
507         private int mMsg;
508         private StatusBarPanel mPanel;
509 
510         public TouchOutsideListener(int msg, StatusBarPanel panel) {
511             mMsg = msg;
512             mPanel = panel;
513         }
514 
515         public boolean onTouch(View v, MotionEvent ev) {
516             final int action = ev.getAction();
517             if (action == MotionEvent.ACTION_OUTSIDE
518                 || (action == MotionEvent.ACTION_DOWN
519                     && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
520                 mHandler.removeMessages(mMsg);
521                 mHandler.sendEmptyMessage(mMsg);
522                 return true;
523             }
524             return false;
525         }
526     }
527 
528     protected void workAroundBadLayerDrawableOpacity(View v) {
529     }
530 
531     protected  boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
532         int rowHeight =
533                 mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height);
534         int minHeight =
535                 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
536         int maxHeight =
537                 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
538         StatusBarNotification sbn = entry.notification;
539         RemoteViews oneU = sbn.notification.contentView;
540         RemoteViews large = sbn.notification.bigContentView;
541         if (oneU == null) {
542             return false;
543         }
544 
545         // create the row view
546         LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
547                 Context.LAYOUT_INFLATER_SERVICE);
548         View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
549 
550         // for blaming (see SwipeHelper.setLongPressListener)
551         row.setTag(sbn.pkg);
552 
553         workAroundBadLayerDrawableOpacity(row);
554         View vetoButton = updateNotificationVetoButton(row, sbn);
555         vetoButton.setContentDescription(mContext.getString(
556                 R.string.accessibility_remove_notification));
557 
558         // NB: the large icon is now handled entirely by the template
559 
560         // bind the click event to the content area
561         ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
562         ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
563 
564         content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
565 
566         PendingIntent contentIntent = sbn.notification.contentIntent;
567         if (contentIntent != null) {
568             final View.OnClickListener listener = new NotificationClicker(contentIntent,
569                     sbn.pkg, sbn.tag, sbn.id);
570             content.setOnClickListener(listener);
571         } else {
572             content.setOnClickListener(null);
573         }
574 
575         // TODO(cwren) normalize variable names with those in updateNotification
576         View expandedOneU = null;
577         View expandedLarge = null;
578         Exception exception = null;
579         try {
580             expandedOneU = oneU.apply(mContext, adaptive, mOnClickHandler);
581             if (large != null) {
582                 expandedLarge = large.apply(mContext, adaptive, mOnClickHandler);
583             }
584         }
585         catch (RuntimeException e) {
586             final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
587             Slog.e(TAG, "couldn't inflate view for notification " + ident, e);
588             return false;
589         }
590 
591         if (expandedOneU != null) {
592             SizeAdaptiveLayout.LayoutParams params =
593                     new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams());
594             params.minHeight = minHeight;
595             params.maxHeight = minHeight;
596             adaptive.addView(expandedOneU, params);
597         }
598         if (expandedLarge != null) {
599             SizeAdaptiveLayout.LayoutParams params =
600                     new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams());
601             params.minHeight = minHeight+1;
602             params.maxHeight = maxHeight;
603             adaptive.addView(expandedLarge, params);
604         }
605         row.setDrawingCacheEnabled(true);
606 
607         applyLegacyRowBackground(sbn, content);
608 
609         row.setTag(R.id.expandable_tag, Boolean.valueOf(large != null));
610         entry.row = row;
611         entry.content = content;
612         entry.expanded = expandedOneU;
613         entry.setLargeView(expandedLarge);
614 
615         return true;
616     }
617 
618     public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
619         return new NotificationClicker(intent, pkg, tag, id);
620     }
621 
622     private class NotificationClicker implements View.OnClickListener {
623         private PendingIntent mIntent;
624         private String mPkg;
625         private String mTag;
626         private int mId;
627 
628         NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
629             mIntent = intent;
630             mPkg = pkg;
631             mTag = tag;
632             mId = id;
633         }
634 
635         public void onClick(View v) {
636             try {
637                 // The intent we are sending is for the application, which
638                 // won't have permission to immediately start an activity after
639                 // the user switches to home.  We know it is safe to do at this
640                 // point, so make sure new activity switches are now allowed.
641                 ActivityManagerNative.getDefault().resumeAppSwitches();
642                 // Also, notifications can be launched from the lock screen,
643                 // so dismiss the lock screen when the activity starts.
644                 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
645             } catch (RemoteException e) {
646             }
647 
648             if (mIntent != null) {
649                 int[] pos = new int[2];
650                 v.getLocationOnScreen(pos);
651                 Intent overlay = new Intent();
652                 overlay.setSourceBounds(
653                         new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
654                 try {
655                     mIntent.send(mContext, 0, overlay);
656                 } catch (PendingIntent.CanceledException e) {
657                     // the stack trace isn't very helpful here.  Just log the exception message.
658                     Slog.w(TAG, "Sending contentIntent failed: " + e);
659                 }
660 
661                 KeyguardManager kgm =
662                     (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
663                 if (kgm != null) kgm.exitKeyguardSecurely(null);
664             }
665 
666             try {
667                 mBarService.onNotificationClick(mPkg, mTag, mId);
668             } catch (RemoteException ex) {
669                 // system process is dead if we're here.
670             }
671 
672             // close the shade if it was open
673             animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE);
674             visibilityChanged(false);
675 
676             // If this click was on the intruder alert, hide that instead
677 //            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
678         }
679     }
680     /**
681      * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
682      * This was added last-minute and is inconsistent with the way the rest of the notifications
683      * are handled, because the notification isn't really cancelled.  The lights are just
684      * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
685      * this is what he wants. (see bug 1131461)
686      */
687     protected void visibilityChanged(boolean visible) {
688         if (mPanelSlightlyVisible != visible) {
689             mPanelSlightlyVisible = visible;
690             try {
691                 mBarService.onPanelRevealed();
692             } catch (RemoteException ex) {
693                 // Won't fail unless the world has ended.
694             }
695         }
696     }
697 
698     /**
699      * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
700      * about the failure.
701      *
702      * WARNING: this will call back into us.  Don't hold any locks.
703      */
704     void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
705         removeNotification(key);
706         try {
707             mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
708         } catch (RemoteException ex) {
709             // The end is nigh.
710         }
711     }
712 
713     protected StatusBarNotification removeNotificationViews(IBinder key) {
714         NotificationData.Entry entry = mNotificationData.remove(key);
715         if (entry == null) {
716             Slog.w(TAG, "removeNotification for unknown key: " + key);
717             return null;
718         }
719         // Remove the expanded view.
720         ViewGroup rowParent = (ViewGroup)entry.row.getParent();
721         if (rowParent != null) rowParent.removeView(entry.row);
722         updateExpansionStates();
723         updateNotificationIcons();
724 
725         return entry.notification;
726     }
727 
728     protected StatusBarIconView addNotificationViews(IBinder key,
729             StatusBarNotification notification) {
730         if (DEBUG) {
731             Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
732         }
733         // Construct the icon.
734         final StatusBarIconView iconView = new StatusBarIconView(mContext,
735                 notification.pkg + "/0x" + Integer.toHexString(notification.id),
736                 notification.notification);
737         iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
738 
739         final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
740                     notification.notification.icon,
741                     notification.notification.iconLevel,
742                     notification.notification.number,
743                     notification.notification.tickerText);
744         if (!iconView.set(ic)) {
745             handleNotificationError(key, notification, "Couldn't create icon: " + ic);
746             return null;
747         }
748         // Construct the expanded view.
749         NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
750         if (!inflateViews(entry, mPile)) {
751             handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
752                     + notification);
753             return null;
754         }
755 
756         // Add the expanded view and icon.
757         int pos = mNotificationData.add(entry);
758         if (DEBUG) {
759             Slog.d(TAG, "addNotificationViews: added at " + pos);
760         }
761         updateExpansionStates();
762         updateNotificationIcons();
763 
764         return iconView;
765     }
766 
767     protected boolean expandView(NotificationData.Entry entry, boolean expand) {
768         int rowHeight =
769                 mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height);
770         ViewGroup.LayoutParams lp = entry.row.getLayoutParams();
771         if (entry.expandable() && expand) {
772             if (DEBUG) Slog.d(TAG, "setting expanded row height to WRAP_CONTENT");
773             lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
774         } else {
775             if (DEBUG) Slog.d(TAG, "setting collapsed row height to " + rowHeight);
776             lp.height = rowHeight;
777         }
778         entry.row.setLayoutParams(lp);
779         return expand;
780     }
781 
782     protected void updateExpansionStates() {
783         int N = mNotificationData.size();
784         for (int i = 0; i < N; i++) {
785             NotificationData.Entry entry = mNotificationData.get(i);
786             if (i == (N-1)) {
787                 if (DEBUG) Slog.d(TAG, "expanding top notification at " + i);
788                 expandView(entry, true);
789             } else {
790                 if (!entry.userExpanded()) {
791                     if (DEBUG) Slog.d(TAG, "collapsing notification at " + i);
792                     expandView(entry, false);
793                 } else {
794                     if (DEBUG) Slog.d(TAG, "ignoring user-modified notification at " + i);
795                 }
796             }
797         }
798     }
799 
800     protected abstract void haltTicker();
801     protected abstract void setAreThereNotifications();
802     protected abstract void updateNotificationIcons();
803     protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
804     protected abstract void updateExpandedViewPos(int expandedPosition);
805     protected abstract int getExpandedViewMaxHeight();
806     protected abstract boolean shouldDisableNavbarGestures();
807 
808     protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
809         return parent != null && parent.indexOfChild(entry.row) == 0;
810     }
811 
812     public void updateNotification(IBinder key, StatusBarNotification notification) {
813         if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
814 
815         final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
816         if (oldEntry == null) {
817             Slog.w(TAG, "updateNotification for unknown key: " + key);
818             return;
819         }
820 
821         final StatusBarNotification oldNotification = oldEntry.notification;
822 
823         // XXX: modify when we do something more intelligent with the two content views
824         final RemoteViews oldContentView = oldNotification.notification.contentView;
825         final RemoteViews contentView = notification.notification.contentView;
826         final RemoteViews oldBigContentView = oldNotification.notification.bigContentView;
827         final RemoteViews bigContentView = notification.notification.bigContentView;
828 
829         if (DEBUG) {
830             Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
831                     + " ongoing=" + oldNotification.isOngoing()
832                     + " expanded=" + oldEntry.expanded
833                     + " contentView=" + oldContentView
834                     + " bigContentView=" + oldBigContentView
835                     + " rowParent=" + oldEntry.row.getParent());
836             Slog.d(TAG, "new notification: when=" + notification.notification.when
837                     + " ongoing=" + oldNotification.isOngoing()
838                     + " contentView=" + contentView
839                     + " bigContentView=" + bigContentView);
840         }
841 
842         // Can we just reapply the RemoteViews in place?  If when didn't change, the order
843         // didn't change.
844 
845         // 1U is never null
846         boolean contentsUnchanged = oldEntry.expanded != null
847                 && contentView.getPackage() != null
848                 && oldContentView.getPackage() != null
849                 && oldContentView.getPackage().equals(contentView.getPackage())
850                 && oldContentView.getLayoutId() == contentView.getLayoutId();
851         // large view may be null
852         boolean bigContentsUnchanged =
853                 (oldEntry.getLargeView() == null && bigContentView == null)
854                 || ((oldEntry.getLargeView() != null && bigContentView != null)
855                     && bigContentView.getPackage() != null
856                     && oldBigContentView.getPackage() != null
857                     && oldBigContentView.getPackage().equals(bigContentView.getPackage())
858                     && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
859         ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
860         boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
861                 && notification.score == oldNotification.score;
862                 // score now encompasses/supersedes isOngoing()
863 
864         boolean updateTicker = notification.notification.tickerText != null
865                 && !TextUtils.equals(notification.notification.tickerText,
866                         oldEntry.notification.notification.tickerText);
867         boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
868         if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
869             if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
870             oldEntry.notification = notification;
871             try {
872                 // Reapply the RemoteViews
873                 contentView.reapply(mContext, oldEntry.expanded, mOnClickHandler);
874                 if (bigContentView != null && oldEntry.getLargeView() != null) {
875                     bigContentView.reapply(mContext, oldEntry.getLargeView(), mOnClickHandler);
876                 }
877                 // update the contentIntent
878                 final PendingIntent contentIntent = notification.notification.contentIntent;
879                 if (contentIntent != null) {
880                     final View.OnClickListener listener = makeClicker(contentIntent,
881                             notification.pkg, notification.tag, notification.id);
882                     oldEntry.content.setOnClickListener(listener);
883                 } else {
884                     oldEntry.content.setOnClickListener(null);
885                 }
886                 // Update the icon.
887                 final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
888                         notification.notification.icon, notification.notification.iconLevel,
889                         notification.notification.number,
890                         notification.notification.tickerText);
891                 if (!oldEntry.icon.set(ic)) {
892                     handleNotificationError(key, notification, "Couldn't update icon: " + ic);
893                     return;
894                 }
895                 updateExpansionStates();
896             }
897             catch (RuntimeException e) {
898                 // It failed to add cleanly.  Log, and remove the view from the panel.
899                 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
900                 removeNotificationViews(key);
901                 addNotificationViews(key, notification);
902             }
903         } else {
904             if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
905             if (DEBUG) Slog.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
906             if (DEBUG) Slog.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
907             if (DEBUG) Slog.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
908             final boolean wasExpanded = oldEntry.userExpanded();
909             removeNotificationViews(key);
910             addNotificationViews(key, notification);
911             if (wasExpanded) {
912                 final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
913                 expandView(newEntry, true);
914                 newEntry.setUserExpanded(true);
915             }
916         }
917 
918         // Update the veto button accordingly (and as a result, whether this row is
919         // swipe-dismissable)
920         updateNotificationVetoButton(oldEntry.row, notification);
921 
922         // Restart the ticker if it's still running
923         if (updateTicker) {
924             haltTicker();
925             tick(key, notification, false);
926         }
927 
928         // Recalculate the position of the sliding windows and the titles.
929         setAreThereNotifications();
930         updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
931 
932         // See if we need to update the intruder.
933         if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) {
934             if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification);
935             // XXX: this is a hack for Alarms. The real implementation will need to *update*
936             // the intruder.
937             if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add()
938                 if (DEBUG) Slog.d(TAG, "no longer intrudes!");
939                 mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
940             }
941         }
942     }
943 
944     // Q: What kinds of notifications should show during setup?
945     // A: Almost none! Only things coming from the system (package is "android") that also
946     // have special "kind" tags marking them as relevant for setup (see below).
947     protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
948         if ("android".equals(sbn.pkg)) {
949             if (sbn.notification.kind != null) {
950                 for (String aKind : sbn.notification.kind) {
951                     // IME switcher, created by InputMethodManagerService
952                     if ("android.system.imeswitcher".equals(aKind)) return true;
953                     // OTA availability & errors, created by SystemUpdateService
954                     if ("android.system.update".equals(aKind)) return true;
955                 }
956             }
957         }
958         return false;
959     }
960 
961     public boolean inKeyguardRestrictedInputMode() {
962         KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
963         return km.inKeyguardRestrictedInputMode();
964     }
965 }
966