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