Home | History | Annotate | Download | only in statusbar
      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      */
    124     protected abstract void createAndAddWindows();
    125 
    126     protected Display mDisplay;
    127     private IWindowManager mWindowManager;
    128     private boolean mDeviceProvisioned = false;
    129 
    130     public IWindowManager getWindowManager() {
    131         return mWindowManager;
    132     }
    133 
    134     public Display getDisplay() {
    135         return mDisplay;
    136     }
    137 
    138     public IStatusBarService getStatusBarService() {
    139         return mBarService;
    140     }
    141 
    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 
    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 
    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 
    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 
    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 
    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