OSDN Git Service

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