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 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 }
   1102