/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java

https://github.com/aizuzi/platform_frameworks_base · Java · 1104 lines · 868 code · 138 blank · 98 comment · 215 complexity · 91e365098d7f41352ae7ce4488bcc562 MD5 · raw file

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