1 /* 2 * Copyright (C) 2007 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.server.status; 18 19 import com.android.internal.R; 20 import com.android.internal.util.CharSequences; 21 22 import android.app.ActivityManagerNative; 23 import android.app.Dialog; 24 import android.app.IStatusBar; 25 import android.app.PendingIntent; 26 import android.app.StatusBarManager; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 32 import android.content.res.Resources; 33 import android.graphics.PixelFormat; 34 import android.graphics.drawable.Drawable; 35 import android.net.Uri; 36 import android.os.IBinder; 37 import android.os.RemoteException; 38 import android.os.Binder; 39 import android.os.Handler; 40 import android.os.Message; 41 import android.os.SystemClock; 42 import android.provider.Telephony; 43 import android.util.Log; 44 import android.view.Display; 45 import android.view.Gravity; 46 import android.view.KeyEvent; 47 import android.view.LayoutInflater; 48 import android.view.MotionEvent; 49 import android.view.VelocityTracker; 50 import android.view.View; 51 import android.view.ViewGroup; 52 import android.view.Window; 53 import android.view.WindowManager; 54 import android.view.WindowManagerImpl; 55 import android.view.animation.Animation; 56 import android.view.animation.AnimationUtils; 57 import android.widget.LinearLayout; 58 import android.widget.RemoteViews; 59 import android.widget.ScrollView; 60 import android.widget.TextView; 61 import android.widget.FrameLayout; 62 63 import java.io.FileDescriptor; 64 import java.io.PrintWriter; 65 import java.util.ArrayList; 66 import java.util.HashMap; 67 import java.util.Set; 68 69 70 /** 71 * The public (ok, semi-public) service for the status bar. 72 * <p> 73 * This interesting thing to note about this class is that most of the methods that 74 * are called from other classes just post a message, and everything else is batched 75 * and coalesced into a series of calls to methods that all start with "perform." 76 * There are two reasons for this. The first is that some of the methods (activate/deactivate) 77 * are on IStatusBar, so they're called from the thread pool and they need to make their 78 * way onto the UI thread. The second is that the message queue is stopped while animations 79 * are happening in order to make for smoother transitions. 80 * <p> 81 * Each icon is either an icon or an icon and a notification. They're treated mostly 82 * separately throughout the code, although they both use the same key, which is assigned 83 * when they are created. 84 */ 85 public class StatusBarService extends IStatusBar.Stub 86 { 87 static final String TAG = "StatusBar"; 88 static final boolean DEBUG = false; 89 static final boolean SPEW = false; 90 static final boolean DBG = false; 91 92 static final int EXPANDED_LEAVE_ALONE = -10000; 93 static final int EXPANDED_FULL_OPEN = -10001; 94 95 private static final int MSG_ANIMATE = 1000; 96 private static final int MSG_ANIMATE_REVEAL = 1001; 97 98 private static final int OP_ADD_ICON = 1; 99 private static final int OP_UPDATE_ICON = 2; 100 private static final int OP_REMOVE_ICON = 3; 101 private static final int OP_SET_VISIBLE = 4; 102 private static final int OP_EXPAND = 5; 103 private static final int OP_TOGGLE = 6; 104 private static final int OP_DISABLE = 7; 105 private class PendingOp { 106 IBinder key; 107 int code; 108 IconData iconData; 109 NotificationData notificationData; 110 boolean visible; 111 int integer; 112 } 113 114 private class DisableRecord implements IBinder.DeathRecipient { 115 String pkg; 116 int what; 117 IBinder token; 118 binderDied()119 public void binderDied() { 120 Log.i(TAG, "binder died for pkg=" + pkg); 121 disable(0, token, pkg); 122 token.unlinkToDeath(this, 0); 123 } 124 } 125 126 public interface NotificationCallbacks { onSetDisabled(int status)127 void onSetDisabled(int status); onClearAll()128 void onClearAll(); onNotificationClick(String pkg, String tag, int id)129 void onNotificationClick(String pkg, String tag, int id); onPanelRevealed()130 void onPanelRevealed(); 131 } 132 133 private class ExpandedDialog extends Dialog { ExpandedDialog(Context context)134 ExpandedDialog(Context context) { 135 super(context, com.android.internal.R.style.Theme_Light_NoTitleBar); 136 } 137 138 @Override dispatchKeyEvent(KeyEvent event)139 public boolean dispatchKeyEvent(KeyEvent event) { 140 boolean down = event.getAction() == KeyEvent.ACTION_DOWN; 141 switch (event.getKeyCode()) { 142 case KeyEvent.KEYCODE_BACK: 143 if (!down) { 144 StatusBarService.this.deactivate(); 145 } 146 return true; 147 } 148 return super.dispatchKeyEvent(event); 149 } 150 } 151 152 final Context mContext; 153 final Display mDisplay; 154 StatusBarView mStatusBarView; 155 int mPixelFormat; 156 H mHandler = new H(); 157 ArrayList<PendingOp> mQueue = new ArrayList<PendingOp>(); 158 NotificationCallbacks mNotificationCallbacks; 159 160 // All accesses to mIconMap and mNotificationData are syncronized on those objects, 161 // but this is only so dump() can work correctly. Modifying these outside of the UI 162 // thread will not work, there are places in the code that unlock and reaquire between 163 // reads and require them to not be modified. 164 165 // icons 166 HashMap<IBinder,StatusBarIcon> mIconMap = new HashMap<IBinder,StatusBarIcon>(); 167 ArrayList<StatusBarIcon> mIconList = new ArrayList<StatusBarIcon>(); 168 String[] mRightIconSlots; 169 StatusBarIcon[] mRightIcons; 170 LinearLayout mIcons; 171 IconMerger mNotificationIcons; 172 LinearLayout mStatusIcons; 173 StatusBarIcon mMoreIcon; 174 private UninstallReceiver mUninstallReceiver; 175 176 // expanded notifications 177 NotificationViewList mNotificationData = new NotificationViewList(); 178 Dialog mExpandedDialog; 179 ExpandedView mExpandedView; 180 WindowManager.LayoutParams mExpandedParams; 181 ScrollView mScrollView; 182 View mNotificationLinearLayout; 183 TextView mOngoingTitle; 184 LinearLayout mOngoingItems; 185 TextView mLatestTitle; 186 LinearLayout mLatestItems; 187 TextView mNoNotificationsTitle; 188 TextView mSpnLabel; 189 TextView mPlmnLabel; 190 TextView mClearButton; 191 CloseDragHandle mCloseView; 192 int[] mCloseLocation = new int[2]; 193 boolean mExpanded; 194 boolean mExpandedVisible; 195 196 // the date view 197 DateView mDateView; 198 199 // the tracker view 200 TrackingView mTrackingView; 201 WindowManager.LayoutParams mTrackingParams; 202 int mTrackingPosition; 203 204 // ticker 205 private Ticker mTicker; 206 private View mTickerView; 207 private boolean mTicking; 208 209 // Tracking finger for opening/closing. 210 boolean mTracking; 211 VelocityTracker mVelocityTracker; 212 213 static final int ANIM_FRAME_DURATION = (1000/60); 214 215 boolean mAnimating; 216 long mCurAnimationTime; 217 float mDisplayHeight; 218 float mAnimY; 219 float mAnimVel; 220 float mAnimAccel; 221 long mAnimLastTime; 222 boolean mAnimatingReveal = false; 223 int mViewDelta; 224 int[] mAbsPos = new int[2]; 225 226 // for disabling the status bar 227 ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); 228 int mDisabled = 0; 229 230 /** 231 * Construct the service, add the status bar view to the window manager 232 */ StatusBarService(Context context)233 public StatusBarService(Context context) { 234 mContext = context; 235 mDisplay = ((WindowManager)context.getSystemService( 236 Context.WINDOW_SERVICE)).getDefaultDisplay(); 237 makeStatusBarView(context); 238 mUninstallReceiver = new UninstallReceiver(); 239 } 240 setNotificationCallbacks(NotificationCallbacks listener)241 public void setNotificationCallbacks(NotificationCallbacks listener) { 242 mNotificationCallbacks = listener; 243 } 244 245 // ================================================================================ 246 // Constructing the view 247 // ================================================================================ makeStatusBarView(Context context)248 private void makeStatusBarView(Context context) { 249 Resources res = context.getResources(); 250 mRightIconSlots = res.getStringArray(com.android.internal.R.array.status_bar_icon_order); 251 mRightIcons = new StatusBarIcon[mRightIconSlots.length]; 252 253 ExpandedView expanded = (ExpandedView)View.inflate(context, 254 com.android.internal.R.layout.status_bar_expanded, null); 255 expanded.mService = this; 256 StatusBarView sb = (StatusBarView)View.inflate(context, 257 com.android.internal.R.layout.status_bar, null); 258 sb.mService = this; 259 260 // figure out which pixel-format to use for the status bar. 261 mPixelFormat = PixelFormat.TRANSLUCENT; 262 Drawable bg = sb.getBackground(); 263 if (bg != null) { 264 mPixelFormat = bg.getOpacity(); 265 } 266 267 mStatusBarView = sb; 268 mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons); 269 mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons); 270 mNotificationIcons.service = this; 271 mIcons = (LinearLayout)sb.findViewById(R.id.icons); 272 mTickerView = sb.findViewById(R.id.ticker); 273 mDateView = (DateView)sb.findViewById(R.id.date); 274 275 mExpandedDialog = new ExpandedDialog(context); 276 mExpandedView = expanded; 277 mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle); 278 mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems); 279 mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle); 280 mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems); 281 mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle); 282 mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button); 283 mClearButton.setOnClickListener(mClearButtonListener); 284 mSpnLabel = (TextView)expanded.findViewById(R.id.spnLabel); 285 mPlmnLabel = (TextView)expanded.findViewById(R.id.plmnLabel); 286 mScrollView = (ScrollView)expanded.findViewById(R.id.scroll); 287 mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout); 288 289 mOngoingTitle.setVisibility(View.GONE); 290 mLatestTitle.setVisibility(View.GONE); 291 292 mTicker = new MyTicker(context, sb); 293 294 TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText); 295 tickerView.mTicker = mTicker; 296 297 mTrackingView = (TrackingView)View.inflate(context, 298 com.android.internal.R.layout.status_bar_tracking, null); 299 mTrackingView.mService = this; 300 mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close); 301 mCloseView.mService = this; 302 303 // add the more icon for the notifications 304 IconData moreData = IconData.makeIcon(null, context.getPackageName(), 305 R.drawable.stat_notify_more, 0, 42); 306 mMoreIcon = new StatusBarIcon(context, moreData, mNotificationIcons); 307 mMoreIcon.view.setId(R.drawable.stat_notify_more); 308 mNotificationIcons.moreIcon = mMoreIcon; 309 mNotificationIcons.addView(mMoreIcon.view); 310 311 // set the inital view visibility 312 setAreThereNotifications(); 313 mDateView.setVisibility(View.INVISIBLE); 314 315 // before we register for broadcasts 316 mPlmnLabel.setText(R.string.lockscreen_carrier_default); 317 mPlmnLabel.setVisibility(View.VISIBLE); 318 mSpnLabel.setText(""); 319 mSpnLabel.setVisibility(View.GONE); 320 321 // receive broadcasts 322 IntentFilter filter = new IntentFilter(); 323 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 324 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 325 filter.addAction(Intent.ACTION_SCREEN_OFF); 326 filter.addAction(Telephony.Intents.SPN_STRINGS_UPDATED_ACTION); 327 context.registerReceiver(mBroadcastReceiver, filter); 328 } 329 systemReady()330 public void systemReady() { 331 final StatusBarView view = mStatusBarView; 332 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 333 ViewGroup.LayoutParams.FILL_PARENT, 334 view.getContext().getResources().getDimensionPixelSize( 335 com.android.internal.R.dimen.status_bar_height), 336 WindowManager.LayoutParams.TYPE_STATUS_BAR, 337 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| 338 WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING, 339 mPixelFormat); 340 lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; 341 lp.setTitle("StatusBar"); 342 lp.windowAnimations = R.style.Animation_StatusBar; 343 344 WindowManagerImpl.getDefault().addView(view, lp); 345 } 346 347 // ================================================================================ 348 // From IStatusBar 349 // ================================================================================ activate()350 public void activate() { 351 enforceExpandStatusBar(); 352 addPendingOp(OP_EXPAND, null, true); 353 } 354 deactivate()355 public void deactivate() { 356 enforceExpandStatusBar(); 357 addPendingOp(OP_EXPAND, null, false); 358 } 359 toggle()360 public void toggle() { 361 enforceExpandStatusBar(); 362 addPendingOp(OP_TOGGLE, null, false); 363 } 364 disable(int what, IBinder token, String pkg)365 public void disable(int what, IBinder token, String pkg) { 366 enforceStatusBar(); 367 synchronized (mNotificationCallbacks) { 368 // This is a little gross, but I think it's safe as long as nobody else 369 // synchronizes on mNotificationCallbacks. It's important that the the callback 370 // and the pending op get done in the correct order and not interleaved with 371 // other calls, otherwise they'll get out of sync. 372 int net; 373 synchronized (mDisableRecords) { 374 manageDisableListLocked(what, token, pkg); 375 net = gatherDisableActionsLocked(); 376 mNotificationCallbacks.onSetDisabled(net); 377 } 378 addPendingOp(OP_DISABLE, net); 379 } 380 } 381 addIcon(String slot, String iconPackage, int iconId, int iconLevel)382 public IBinder addIcon(String slot, String iconPackage, int iconId, int iconLevel) { 383 enforceStatusBar(); 384 return addIcon(IconData.makeIcon(slot, iconPackage, iconId, iconLevel, 0), null); 385 } 386 updateIcon(IBinder key, String slot, String iconPackage, int iconId, int iconLevel)387 public void updateIcon(IBinder key, 388 String slot, String iconPackage, int iconId, int iconLevel) { 389 enforceStatusBar(); 390 updateIcon(key, IconData.makeIcon(slot, iconPackage, iconId, iconLevel, 0), null); 391 } 392 removeIcon(IBinder key)393 public void removeIcon(IBinder key) { 394 enforceStatusBar(); 395 addPendingOp(OP_REMOVE_ICON, key, null, null, -1); 396 } 397 enforceStatusBar()398 private void enforceStatusBar() { 399 mContext.enforceCallingOrSelfPermission( 400 android.Manifest.permission.STATUS_BAR, 401 "StatusBarService"); 402 } 403 enforceExpandStatusBar()404 private void enforceExpandStatusBar() { 405 mContext.enforceCallingOrSelfPermission( 406 android.Manifest.permission.EXPAND_STATUS_BAR, 407 "StatusBarService"); 408 } 409 410 // ================================================================================ 411 // Can be called from any thread 412 // ================================================================================ addIcon(IconData data, NotificationData n)413 public IBinder addIcon(IconData data, NotificationData n) { 414 int slot; 415 // assert early-on if they using a slot that doesn't exist. 416 if (data != null && n == null) { 417 slot = getRightIconIndex(data.slot); 418 if (slot < 0) { 419 throw new SecurityException("invalid status bar icon slot: " 420 + (data.slot != null ? "'" + data.slot + "'" : "null")); 421 } 422 } else { 423 slot = -1; 424 } 425 IBinder key = new Binder(); 426 addPendingOp(OP_ADD_ICON, key, data, n, -1); 427 return key; 428 } 429 updateIcon(IBinder key, IconData data, NotificationData n)430 public void updateIcon(IBinder key, IconData data, NotificationData n) { 431 addPendingOp(OP_UPDATE_ICON, key, data, n, -1); 432 } 433 setIconVisibility(IBinder key, boolean visible)434 public void setIconVisibility(IBinder key, boolean visible) { 435 addPendingOp(OP_SET_VISIBLE, key, visible); 436 } 437 addPendingOp(int code, IBinder key, IconData data, NotificationData n, int i)438 private void addPendingOp(int code, IBinder key, IconData data, NotificationData n, int i) { 439 synchronized (mQueue) { 440 PendingOp op = new PendingOp(); 441 op.key = key; 442 op.code = code; 443 op.iconData = data == null ? null : data.clone(); 444 op.notificationData = n; 445 op.integer = i; 446 mQueue.add(op); 447 if (mQueue.size() == 1) { 448 mHandler.sendEmptyMessage(2); 449 } 450 } 451 } 452 addPendingOp(int code, IBinder key, boolean visible)453 private void addPendingOp(int code, IBinder key, boolean visible) { 454 synchronized (mQueue) { 455 PendingOp op = new PendingOp(); 456 op.key = key; 457 op.code = code; 458 op.visible = visible; 459 mQueue.add(op); 460 if (mQueue.size() == 1) { 461 mHandler.sendEmptyMessage(1); 462 } 463 } 464 } 465 addPendingOp(int code, int integer)466 private void addPendingOp(int code, int integer) { 467 synchronized (mQueue) { 468 PendingOp op = new PendingOp(); 469 op.code = code; 470 op.integer = integer; 471 mQueue.add(op); 472 if (mQueue.size() == 1) { 473 mHandler.sendEmptyMessage(1); 474 } 475 } 476 } 477 478 // lock on mDisableRecords manageDisableListLocked(int what, IBinder token, String pkg)479 void manageDisableListLocked(int what, IBinder token, String pkg) { 480 if (SPEW) { 481 Log.d(TAG, "manageDisableList what=0x" + Integer.toHexString(what) 482 + " pkg=" + pkg); 483 } 484 // update the list 485 synchronized (mDisableRecords) { 486 final int N = mDisableRecords.size(); 487 DisableRecord tok = null; 488 int i; 489 for (i=0; i<N; i++) { 490 DisableRecord t = mDisableRecords.get(i); 491 if (t.token == token) { 492 tok = t; 493 break; 494 } 495 } 496 if (what == 0 || !token.isBinderAlive()) { 497 if (tok != null) { 498 mDisableRecords.remove(i); 499 tok.token.unlinkToDeath(tok, 0); 500 } 501 } else { 502 if (tok == null) { 503 tok = new DisableRecord(); 504 try { 505 token.linkToDeath(tok, 0); 506 } 507 catch (RemoteException ex) { 508 return; // give up 509 } 510 mDisableRecords.add(tok); 511 } 512 tok.what = what; 513 tok.token = token; 514 tok.pkg = pkg; 515 } 516 } 517 } 518 519 // lock on mDisableRecords gatherDisableActionsLocked()520 int gatherDisableActionsLocked() { 521 final int N = mDisableRecords.size(); 522 // gather the new net flags 523 int net = 0; 524 for (int i=0; i<N; i++) { 525 net |= mDisableRecords.get(i).what; 526 } 527 return net; 528 } 529 getRightIconIndex(String slot)530 private int getRightIconIndex(String slot) { 531 final int N = mRightIconSlots.length; 532 for (int i=0; i<N; i++) { 533 if (mRightIconSlots[i].equals(slot)) { 534 return i; 535 } 536 } 537 return -1; 538 } 539 540 // ================================================================================ 541 // Always called from UI thread 542 // ================================================================================ 543 /** 544 * All changes to the status bar and notifications funnel through here and are batched. 545 */ 546 private class H extends Handler { handleMessage(Message m)547 public void handleMessage(Message m) { 548 if (m.what == MSG_ANIMATE) { 549 doAnimation(); 550 return; 551 } 552 if (m.what == MSG_ANIMATE_REVEAL) { 553 doRevealAnimation(); 554 return; 555 } 556 synchronized (mQueue) { 557 boolean wasExpanded = mExpanded; 558 559 // for each one in the queue, find all of the ones with the same key 560 // and collapse that down into a final op and/or call to setVisibility, etc 561 boolean expand = wasExpanded; 562 boolean doExpand = false; 563 boolean doDisable = false; 564 int disableWhat = 0; 565 int N = mQueue.size(); 566 while (N > 0) { 567 PendingOp op = mQueue.get(0); 568 boolean doOp = false; 569 boolean visible = false; 570 boolean doVisibility = false; 571 if (op.code == OP_SET_VISIBLE) { 572 doVisibility = true; 573 visible = op.visible; 574 } 575 else if (op.code == OP_EXPAND) { 576 doExpand = true; 577 expand = op.visible; 578 } 579 else if (op.code == OP_TOGGLE) { 580 doExpand = true; 581 expand = !expand; 582 } 583 else { 584 doOp = true; 585 } 586 587 if (alwaysHandle(op.code)) { 588 // coalesce these 589 for (int i=1; i<N; i++) { 590 PendingOp o = mQueue.get(i); 591 if (!alwaysHandle(o.code) && o.key == op.key) { 592 if (o.code == OP_SET_VISIBLE) { 593 visible = o.visible; 594 doVisibility = true; 595 } 596 else if (o.code == OP_EXPAND) { 597 expand = o.visible; 598 doExpand = true; 599 } 600 else { 601 op.code = o.code; 602 op.iconData = o.iconData; 603 op.notificationData = o.notificationData; 604 } 605 mQueue.remove(i); 606 i--; 607 N--; 608 } 609 } 610 } 611 612 mQueue.remove(0); 613 N--; 614 615 if (doOp) { 616 switch (op.code) { 617 case OP_ADD_ICON: 618 case OP_UPDATE_ICON: 619 performAddUpdateIcon(op.key, op.iconData, op.notificationData); 620 break; 621 case OP_REMOVE_ICON: 622 performRemoveIcon(op.key); 623 break; 624 case OP_DISABLE: 625 doDisable = true; 626 disableWhat = op.integer; 627 break; 628 } 629 } 630 if (doVisibility && op.code != OP_REMOVE_ICON) { 631 performSetIconVisibility(op.key, visible); 632 } 633 } 634 635 if (mQueue.size() != 0) { 636 throw new RuntimeException("Assertion failed: mQueue.size=" + mQueue.size()); 637 } 638 if (doExpand) { 639 // this is last so that we capture all of the pending changes before doing it 640 if (expand) { 641 animateExpand(); 642 } else { 643 animateCollapse(); 644 } 645 } 646 if (doDisable) { 647 performDisableActions(disableWhat); 648 } 649 } 650 } 651 } 652 alwaysHandle(int code)653 private boolean alwaysHandle(int code) { 654 return code == OP_DISABLE; 655 } 656 performAddUpdateIcon(IBinder key, IconData data, NotificationData n)657 /* private */ void performAddUpdateIcon(IBinder key, IconData data, NotificationData n) 658 throws StatusBarException { 659 if (DBG) { 660 Log.d(TAG, "performAddUpdateIcon icon=" + data + " notification=" + n + " key=" + key); 661 } 662 // notification 663 if (n != null) { 664 StatusBarNotification notification = getNotification(key); 665 NotificationData oldData = null; 666 if (notification == null) { 667 // add 668 notification = new StatusBarNotification(); 669 notification.key = key; 670 notification.data = n; 671 synchronized (mNotificationData) { 672 mNotificationData.add(notification); 673 } 674 addNotificationView(notification); 675 setAreThereNotifications(); 676 } else { 677 // update 678 oldData = notification.data; 679 notification.data = n; 680 updateNotificationView(notification, oldData); 681 } 682 // Show the ticker if one is requested, and the text is different 683 // than the currently displayed ticker. Also don't do this 684 // until status bar window is attached to the window manager, 685 // because... well, what's the point otherwise? And trying to 686 // run a ticker without being attached will crash! 687 if (n.tickerText != null && mStatusBarView.getWindowToken() != null 688 && (oldData == null 689 || oldData.tickerText == null 690 || !CharSequences.equals(oldData.tickerText, n.tickerText))) { 691 if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { 692 mTicker.addEntry(n, StatusBarIcon.getIcon(mContext, data), n.tickerText); 693 } 694 } 695 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 696 } 697 698 // icon 699 synchronized (mIconMap) { 700 StatusBarIcon icon = mIconMap.get(key); 701 if (icon == null) { 702 // add 703 LinearLayout v = n == null ? mStatusIcons : mNotificationIcons; 704 705 icon = new StatusBarIcon(mContext, data, v); 706 mIconMap.put(key, icon); 707 mIconList.add(icon); 708 709 if (n == null) { 710 int slotIndex = getRightIconIndex(data.slot); 711 StatusBarIcon[] rightIcons = mRightIcons; 712 if (rightIcons[slotIndex] == null) { 713 int pos = 0; 714 for (int i=mRightIcons.length-1; i>slotIndex; i--) { 715 StatusBarIcon ic = rightIcons[i]; 716 if (ic != null) { 717 pos++; 718 } 719 } 720 rightIcons[slotIndex] = icon; 721 mStatusIcons.addView(icon.view, pos); 722 } else { 723 Log.e(TAG, "duplicate icon in slot " + slotIndex + "/" + data.slot); 724 mIconMap.remove(key); 725 mIconList.remove(icon); 726 return ; 727 } 728 } else { 729 int iconIndex = mNotificationData.getIconIndex(n); 730 mNotificationIcons.addView(icon.view, iconIndex); 731 } 732 } else { 733 if (n == null) { 734 // right hand side icons -- these don't reorder 735 icon.update(mContext, data); 736 } else { 737 // remove old 738 ViewGroup parent = (ViewGroup)icon.view.getParent(); 739 parent.removeView(icon.view); 740 // add new 741 icon.update(mContext, data); 742 int iconIndex = mNotificationData.getIconIndex(n); 743 mNotificationIcons.addView(icon.view, iconIndex); 744 } 745 } 746 } 747 } 748 performSetIconVisibility(IBinder key, boolean visible)749 /* private */ void performSetIconVisibility(IBinder key, boolean visible) { 750 synchronized (mIconMap) { 751 if (DBG) { 752 Log.d(TAG, "performSetIconVisibility key=" + key + " visible=" + visible); 753 } 754 StatusBarIcon icon = mIconMap.get(key); 755 icon.view.setVisibility(visible ? View.VISIBLE : View.GONE); 756 } 757 } 758 performRemoveIcon(IBinder key)759 /* private */ void performRemoveIcon(IBinder key) { 760 synchronized (this) { 761 if (DBG) { 762 Log.d(TAG, "performRemoveIcon key=" + key); 763 } 764 StatusBarIcon icon = mIconMap.remove(key); 765 mIconList.remove(icon); 766 if (icon != null) { 767 ViewGroup parent = (ViewGroup)icon.view.getParent(); 768 parent.removeView(icon.view); 769 int slotIndex = getRightIconIndex(icon.mData.slot); 770 if (slotIndex >= 0) { 771 mRightIcons[slotIndex] = null; 772 } 773 } 774 StatusBarNotification notification = getNotification(key); 775 if (notification != null) { 776 removeNotificationView(notification); 777 synchronized (mNotificationData) { 778 mNotificationData.remove(notification); 779 } 780 setAreThereNotifications(); 781 } 782 } 783 } 784 getIconNumberForView(View v)785 int getIconNumberForView(View v) { 786 synchronized (mIconMap) { 787 StatusBarIcon icon = null; 788 final int N = mIconList.size(); 789 for (int i=0; i<N; i++) { 790 StatusBarIcon ic = mIconList.get(i); 791 if (ic.view == v) { 792 icon = ic; 793 break; 794 } 795 } 796 if (icon != null) { 797 return icon.getNumber(); 798 } else { 799 return -1; 800 } 801 } 802 } 803 804 getNotification(IBinder key)805 StatusBarNotification getNotification(IBinder key) { 806 synchronized (mNotificationData) { 807 return mNotificationData.get(key); 808 } 809 } 810 811 View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { 812 public void onFocusChange(View v, boolean hasFocus) { 813 // Because 'v' is a ViewGroup, all its children will be (un)selected 814 // too, which allows marqueeing to work. 815 v.setSelected(hasFocus); 816 } 817 }; 818 makeNotificationView(StatusBarNotification notification, ViewGroup parent)819 View makeNotificationView(StatusBarNotification notification, ViewGroup parent) { 820 NotificationData n = notification.data; 821 RemoteViews remoteViews = n.contentView; 822 if (remoteViews == null) { 823 return null; 824 } 825 826 // create the row view 827 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 828 Context.LAYOUT_INFLATER_SERVICE); 829 View row = inflater.inflate(com.android.internal.R.layout.status_bar_latest_event, parent, false); 830 831 // bind the click event to the content area 832 ViewGroup content = (ViewGroup)row.findViewById(com.android.internal.R.id.content); 833 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 834 content.setOnFocusChangeListener(mFocusChangeListener); 835 PendingIntent contentIntent = n.contentIntent; 836 if (contentIntent != null) { 837 content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.tag, n.id)); 838 } 839 840 View child = null; 841 Exception exception = null; 842 try { 843 child = remoteViews.apply(mContext, content); 844 } 845 catch (RuntimeException e) { 846 exception = e; 847 } 848 if (child == null) { 849 Log.e(TAG, "couldn't inflate view for package " + n.pkg, exception); 850 return null; 851 } 852 content.addView(child); 853 854 row.setDrawingCacheEnabled(true); 855 856 notification.view = row; 857 notification.contentView = child; 858 859 return row; 860 } 861 addNotificationView(StatusBarNotification notification)862 void addNotificationView(StatusBarNotification notification) { 863 if (notification.view != null) { 864 throw new RuntimeException("Assertion failed: notification.view=" 865 + notification.view); 866 } 867 868 LinearLayout parent = notification.data.ongoingEvent ? mOngoingItems : mLatestItems; 869 870 View child = makeNotificationView(notification, parent); 871 if (child == null) { 872 return ; 873 } 874 875 int index = mNotificationData.getExpandedIndex(notification); 876 parent.addView(child, index); 877 } 878 879 /** 880 * Remove the old one and put the new one in its place. 881 * @param notification the notification 882 */ updateNotificationView(StatusBarNotification notification, NotificationData oldData)883 void updateNotificationView(StatusBarNotification notification, NotificationData oldData) { 884 NotificationData n = notification.data; 885 if (oldData != null && n != null 886 && n.contentView != null && oldData.contentView != null 887 && n.contentView.getPackage() != null 888 && oldData.contentView.getPackage() != null 889 && oldData.contentView.getPackage().equals(n.contentView.getPackage()) 890 && oldData.contentView.getLayoutId() == n.contentView.getLayoutId()) { 891 mNotificationData.update(notification); 892 try { 893 n.contentView.reapply(mContext, notification.contentView); 894 895 // update the contentIntent 896 ViewGroup content = (ViewGroup)notification.view.findViewById( 897 com.android.internal.R.id.content); 898 PendingIntent contentIntent = n.contentIntent; 899 if (contentIntent != null) { 900 content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.tag, n.id)); 901 } 902 } 903 catch (RuntimeException e) { 904 // It failed to add cleanly. Log, and remove the view from the panel. 905 Log.w(TAG, "couldn't reapply views for package " + n.contentView.getPackage(), e); 906 removeNotificationView(notification); 907 } 908 } else { 909 mNotificationData.update(notification); 910 removeNotificationView(notification); 911 addNotificationView(notification); 912 } 913 setAreThereNotifications(); 914 } 915 removeNotificationView(StatusBarNotification notification)916 void removeNotificationView(StatusBarNotification notification) { 917 View v = notification.view; 918 if (v != null) { 919 ViewGroup parent = (ViewGroup)v.getParent(); 920 parent.removeView(v); 921 notification.view = null; 922 } 923 } 924 setAreThereNotifications()925 private void setAreThereNotifications() { 926 boolean ongoing = mOngoingItems.getChildCount() != 0; 927 boolean latest = mLatestItems.getChildCount() != 0; 928 929 if (mNotificationData.hasClearableItems()) { 930 mClearButton.setVisibility(View.VISIBLE); 931 } else { 932 mClearButton.setVisibility(View.INVISIBLE); 933 } 934 935 mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE); 936 mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE); 937 938 if (ongoing || latest) { 939 mNoNotificationsTitle.setVisibility(View.GONE); 940 } else { 941 mNoNotificationsTitle.setVisibility(View.VISIBLE); 942 } 943 } 944 makeExpandedVisible()945 private void makeExpandedVisible() { 946 if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); 947 if (mExpandedVisible) { 948 return; 949 } 950 mExpandedVisible = true; 951 panelSlightlyVisible(true); 952 953 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 954 mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 955 mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 956 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 957 mExpandedView.requestFocus(View.FOCUS_FORWARD); 958 mTrackingView.setVisibility(View.VISIBLE); 959 960 if (!mTicking) { 961 setDateViewVisibility(true, com.android.internal.R.anim.fade_in); 962 } 963 } 964 animateExpand()965 void animateExpand() { 966 if (SPEW) Log.d(TAG, "Animate expand: expanded=" + mExpanded); 967 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 968 return ; 969 } 970 if (mExpanded) { 971 return; 972 } 973 974 prepareTracking(0); 975 performFling(0, 2000.0f, true); 976 } 977 animateCollapse()978 void animateCollapse() { 979 if (SPEW) { 980 Log.d(TAG, "animateCollapse(): mExpanded=" + mExpanded 981 + " mExpandedVisible=" + mExpandedVisible 982 + " mAnimating=" + mAnimating 983 + " mAnimVel=" + mAnimVel); 984 } 985 986 if (!mExpandedVisible) { 987 return; 988 } 989 990 if (mAnimating) { 991 return; 992 } 993 994 int y = mDisplay.getHeight()-1; 995 prepareTracking(y); 996 performFling(y, -2000.0f, true); 997 } 998 performExpand()999 void performExpand() { 1000 if (SPEW) Log.d(TAG, "Perform expand: expanded=" + mExpanded); 1001 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 1002 return ; 1003 } 1004 if (mExpanded) { 1005 return; 1006 } 1007 1008 // It seems strange to sometimes not expand... 1009 if (false) { 1010 synchronized (mNotificationData) { 1011 if (mNotificationData.size() == 0) { 1012 return; 1013 } 1014 } 1015 } 1016 1017 mExpanded = true; 1018 makeExpandedVisible(); 1019 updateExpandedViewPos(EXPANDED_FULL_OPEN); 1020 1021 if (false) postStartTracing(); 1022 } 1023 performCollapse()1024 void performCollapse() { 1025 if (SPEW) Log.d(TAG, "Perform collapse: expanded=" + mExpanded 1026 + " expanded visible=" + mExpandedVisible); 1027 1028 if (!mExpandedVisible) { 1029 return; 1030 } 1031 mExpandedVisible = false; 1032 panelSlightlyVisible(false); 1033 mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1034 mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1035 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 1036 mTrackingView.setVisibility(View.GONE); 1037 1038 if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { 1039 setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); 1040 } 1041 setDateViewVisibility(false, com.android.internal.R.anim.fade_out); 1042 1043 if (!mExpanded) { 1044 return; 1045 } 1046 mExpanded = false; 1047 } 1048 doAnimation()1049 void doAnimation() { 1050 if (mAnimating) { 1051 if (SPEW) Log.d(TAG, "doAnimation"); 1052 if (SPEW) Log.d(TAG, "doAnimation before mAnimY=" + mAnimY); 1053 incrementAnim(); 1054 if (SPEW) Log.d(TAG, "doAnimation after mAnimY=" + mAnimY); 1055 if (mAnimY >= mDisplay.getHeight()-1) { 1056 if (SPEW) Log.d(TAG, "Animation completed to expanded state."); 1057 mAnimating = false; 1058 updateExpandedViewPos(EXPANDED_FULL_OPEN); 1059 performExpand(); 1060 } 1061 else if (mAnimY < mStatusBarView.getHeight()) { 1062 if (SPEW) Log.d(TAG, "Animation completed to collapsed state."); 1063 mAnimating = false; 1064 updateExpandedViewPos(0); 1065 performCollapse(); 1066 } 1067 else { 1068 updateExpandedViewPos((int)mAnimY); 1069 mCurAnimationTime += ANIM_FRAME_DURATION; 1070 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); 1071 } 1072 } 1073 } 1074 stopTracking()1075 void stopTracking() { 1076 mTracking = false; 1077 mVelocityTracker.recycle(); 1078 mVelocityTracker = null; 1079 } 1080 incrementAnim()1081 void incrementAnim() { 1082 long now = SystemClock.uptimeMillis(); 1083 float t = ((float)(now - mAnimLastTime)) / 1000; // ms -> s 1084 final float y = mAnimY; 1085 final float v = mAnimVel; // px/s 1086 final float a = mAnimAccel; // px/s/s 1087 mAnimY = y + (v*t) + (0.5f*a*t*t); // px 1088 mAnimVel = v + (a*t); // px/s 1089 mAnimLastTime = now; // ms 1090 //Log.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY 1091 // + " mAnimAccel=" + mAnimAccel); 1092 } 1093 doRevealAnimation()1094 void doRevealAnimation() { 1095 final int h = mCloseView.getHeight() + mStatusBarView.getHeight(); 1096 if (mAnimatingReveal && mAnimating && mAnimY < h) { 1097 incrementAnim(); 1098 if (mAnimY >= h) { 1099 mAnimY = h; 1100 updateExpandedViewPos((int)mAnimY); 1101 } else { 1102 updateExpandedViewPos((int)mAnimY); 1103 mCurAnimationTime += ANIM_FRAME_DURATION; 1104 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), 1105 mCurAnimationTime); 1106 } 1107 } 1108 } 1109 prepareTracking(int y)1110 void prepareTracking(int y) { 1111 mTracking = true; 1112 mVelocityTracker = VelocityTracker.obtain(); 1113 boolean opening = !mExpanded; 1114 if (opening) { 1115 mAnimAccel = 2000.0f; 1116 mAnimVel = 200; 1117 mAnimY = mStatusBarView.getHeight(); 1118 updateExpandedViewPos((int)mAnimY); 1119 mAnimating = true; 1120 mAnimatingReveal = true; 1121 mHandler.removeMessages(MSG_ANIMATE); 1122 mHandler.removeMessages(MSG_ANIMATE_REVEAL); 1123 long now = SystemClock.uptimeMillis(); 1124 mAnimLastTime = now; 1125 mCurAnimationTime = now + ANIM_FRAME_DURATION; 1126 mAnimating = true; 1127 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), 1128 mCurAnimationTime); 1129 makeExpandedVisible(); 1130 } else { 1131 // it's open, close it? 1132 if (mAnimating) { 1133 mAnimating = false; 1134 mHandler.removeMessages(MSG_ANIMATE); 1135 } 1136 updateExpandedViewPos(y + mViewDelta); 1137 } 1138 } 1139 performFling(int y, float vel, boolean always)1140 void performFling(int y, float vel, boolean always) { 1141 mAnimatingReveal = false; 1142 mDisplayHeight = mDisplay.getHeight(); 1143 1144 mAnimY = y; 1145 mAnimVel = vel; 1146 1147 //Log.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); 1148 1149 if (mExpanded) { 1150 if (!always && ( 1151 vel > 200.0f 1152 || (y > (mDisplayHeight-25) && vel > -200.0f))) { 1153 // We are expanded, but they didn't move sufficiently to cause 1154 // us to retract. Animate back to the expanded position. 1155 mAnimAccel = 2000.0f; 1156 if (vel < 0) { 1157 mAnimVel = 0; 1158 } 1159 } 1160 else { 1161 // We are expanded and are now going to animate away. 1162 mAnimAccel = -2000.0f; 1163 if (vel > 0) { 1164 mAnimVel = 0; 1165 } 1166 } 1167 } else { 1168 if (always || ( 1169 vel > 200.0f 1170 || (y > (mDisplayHeight/2) && vel > -200.0f))) { 1171 // We are collapsed, and they moved enough to allow us to 1172 // expand. Animate in the notifications. 1173 mAnimAccel = 2000.0f; 1174 if (vel < 0) { 1175 mAnimVel = 0; 1176 } 1177 } 1178 else { 1179 // We are collapsed, but they didn't move sufficiently to cause 1180 // us to retract. Animate back to the collapsed position. 1181 mAnimAccel = -2000.0f; 1182 if (vel > 0) { 1183 mAnimVel = 0; 1184 } 1185 } 1186 } 1187 //Log.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel 1188 // + " mAnimAccel=" + mAnimAccel); 1189 1190 long now = SystemClock.uptimeMillis(); 1191 mAnimLastTime = now; 1192 mCurAnimationTime = now + ANIM_FRAME_DURATION; 1193 mAnimating = true; 1194 mHandler.removeMessages(MSG_ANIMATE); 1195 mHandler.removeMessages(MSG_ANIMATE_REVEAL); 1196 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); 1197 stopTracking(); 1198 } 1199 interceptTouchEvent(MotionEvent event)1200 boolean interceptTouchEvent(MotionEvent event) { 1201 if (SPEW) Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event); 1202 1203 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 1204 return true; 1205 } 1206 1207 final int statusBarSize = mStatusBarView.getHeight(); 1208 final int hitSize = statusBarSize*2; 1209 if (event.getAction() == MotionEvent.ACTION_DOWN) { 1210 int y = (int)event.getRawY(); 1211 1212 if (!mExpanded) { 1213 mViewDelta = statusBarSize - y; 1214 } else { 1215 mTrackingView.getLocationOnScreen(mAbsPos); 1216 mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y; 1217 } 1218 if ((!mExpanded && y < hitSize) || 1219 (mExpanded && y > (mDisplay.getHeight()-hitSize))) { 1220 prepareTracking(y); 1221 mVelocityTracker.addMovement(event); 1222 } 1223 } else if (mTracking) { 1224 mVelocityTracker.addMovement(event); 1225 final int minY = statusBarSize + mCloseView.getHeight(); 1226 if (event.getAction() == MotionEvent.ACTION_MOVE) { 1227 int y = (int)event.getRawY(); 1228 if (mAnimatingReveal && y < minY) { 1229 // nothing 1230 } else { 1231 mAnimatingReveal = false; 1232 updateExpandedViewPos(y + mViewDelta); 1233 } 1234 } else if (event.getAction() == MotionEvent.ACTION_UP) { 1235 mVelocityTracker.computeCurrentVelocity(1000); 1236 1237 float yVel = mVelocityTracker.getYVelocity(); 1238 boolean negative = yVel < 0; 1239 1240 float xVel = mVelocityTracker.getXVelocity(); 1241 if (xVel < 0) { 1242 xVel = -xVel; 1243 } 1244 if (xVel > 150.0f) { 1245 xVel = 150.0f; // limit how much we care about the x axis 1246 } 1247 1248 float vel = (float)Math.hypot(yVel, xVel); 1249 if (negative) { 1250 vel = -vel; 1251 } 1252 1253 performFling((int)event.getRawY(), vel, false); 1254 } 1255 1256 } 1257 return false; 1258 } 1259 1260 private class Launcher implements View.OnClickListener { 1261 private PendingIntent mIntent; 1262 private String mPkg; 1263 private String mTag; 1264 private int mId; 1265 1266 Launcher(PendingIntent intent, String pkg, String tag, int id) { 1267 mIntent = intent; 1268 mPkg = pkg; 1269 mTag = tag; 1270 mId = id; 1271 } 1272 1273 public void onClick(View v) { 1274 try { 1275 // The intent we are sending is for the application, which 1276 // won't have permission to immediately start an activity after 1277 // the user switches to home. We know it is safe to do at this 1278 // point, so make sure new activity switches are now allowed. 1279 ActivityManagerNative.getDefault().resumeAppSwitches(); 1280 } catch (RemoteException e) { 1281 } 1282 try { 1283 mIntent.send(); 1284 mNotificationCallbacks.onNotificationClick(mPkg, mTag, mId); 1285 } catch (PendingIntent.CanceledException e) { 1286 // the stack trace isn't very helpful here. Just log the exception message. 1287 Log.w(TAG, "Sending contentIntent failed: " + e); 1288 } 1289 deactivate(); 1290 } 1291 } 1292 1293 private class MyTicker extends Ticker { 1294 MyTicker(Context context, StatusBarView sb) { 1295 super(context, sb); 1296 } 1297 1298 @Override 1299 void tickerStarting() { 1300 mTicking = true; 1301 mIcons.setVisibility(View.GONE); 1302 mTickerView.setVisibility(View.VISIBLE); 1303 mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null)); 1304 mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null)); 1305 if (mExpandedVisible) { 1306 setDateViewVisibility(false, com.android.internal.R.anim.push_up_out); 1307 } 1308 } 1309 1310 @Override 1311 void tickerDone() { 1312 mIcons.setVisibility(View.VISIBLE); 1313 mTickerView.setVisibility(View.GONE); 1314 mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null)); 1315 mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out, 1316 mTickingDoneListener)); 1317 if (mExpandedVisible) { 1318 setDateViewVisibility(true, com.android.internal.R.anim.push_down_in); 1319 } 1320 } 1321 1322 void tickerHalting() { 1323 mIcons.setVisibility(View.VISIBLE); 1324 mTickerView.setVisibility(View.GONE); 1325 mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); 1326 mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out, 1327 mTickingDoneListener)); 1328 if (mExpandedVisible) { 1329 setDateViewVisibility(true, com.android.internal.R.anim.fade_in); 1330 } 1331 } 1332 } 1333 1334 Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {; 1335 public void onAnimationEnd(Animation animation) { 1336 mTicking = false; 1337 } 1338 public void onAnimationRepeat(Animation animation) { 1339 } 1340 public void onAnimationStart(Animation animation) { 1341 } 1342 }; 1343 1344 private Animation loadAnim(int id, Animation.AnimationListener listener) { 1345 Animation anim = AnimationUtils.loadAnimation(mContext, id); 1346 if (listener != null) { 1347 anim.setAnimationListener(listener); 1348 } 1349 return anim; 1350 } 1351 1352 public String viewInfo(View v) { 1353 return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() 1354 + " " + v.getWidth() + "x" + v.getHeight() + ")"; 1355 } 1356 1357 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1358 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 1359 != PackageManager.PERMISSION_GRANTED) { 1360 pw.println("Permission Denial: can't dump StatusBar from from pid=" 1361 + Binder.getCallingPid() 1362 + ", uid=" + Binder.getCallingUid()); 1363 return; 1364 } 1365 1366 synchronized (mQueue) { 1367 pw.println("Current Status Bar state:"); 1368 pw.println(" mExpanded=" + mExpanded 1369 + ", mExpandedVisible=" + mExpandedVisible); 1370 pw.println(" mTicking=" + mTicking); 1371 pw.println(" mTracking=" + mTracking); 1372 pw.println(" mAnimating=" + mAnimating 1373 + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel 1374 + ", mAnimAccel=" + mAnimAccel); 1375 pw.println(" mCurAnimationTime=" + mCurAnimationTime 1376 + " mAnimLastTime=" + mAnimLastTime); 1377 pw.println(" mDisplayHeight=" + mDisplayHeight 1378 + " mAnimatingReveal=" + mAnimatingReveal 1379 + " mViewDelta=" + mViewDelta); 1380 pw.println(" mDisplayHeight=" + mDisplayHeight); 1381 final int N = mQueue.size(); 1382 pw.println(" mQueue.size=" + N); 1383 for (int i=0; i<N; i++) { 1384 PendingOp op = mQueue.get(i); 1385 pw.println(" [" + i + "] key=" + op.key + " code=" + op.code + " visible=" 1386 + op.visible); 1387 pw.println(" iconData=" + op.iconData); 1388 pw.println(" notificationData=" + op.notificationData); 1389 } 1390 pw.println(" mExpandedParams: " + mExpandedParams); 1391 pw.println(" mExpandedView: " + viewInfo(mExpandedView)); 1392 pw.println(" mExpandedDialog: " + mExpandedDialog); 1393 pw.println(" mTrackingParams: " + mTrackingParams); 1394 pw.println(" mTrackingView: " + viewInfo(mTrackingView)); 1395 pw.println(" mOngoingTitle: " + viewInfo(mOngoingTitle)); 1396 pw.println(" mOngoingItems: " + viewInfo(mOngoingItems)); 1397 pw.println(" mLatestTitle: " + viewInfo(mLatestTitle)); 1398 pw.println(" mLatestItems: " + viewInfo(mLatestItems)); 1399 pw.println(" mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle)); 1400 pw.println(" mCloseView: " + viewInfo(mCloseView)); 1401 pw.println(" mTickerView: " + viewInfo(mTickerView)); 1402 pw.println(" mScrollView: " + viewInfo(mScrollView) 1403 + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY()); 1404 pw.println("mNotificationLinearLayout: " + viewInfo(mNotificationLinearLayout)); 1405 } 1406 synchronized (mIconMap) { 1407 final int N = mIconMap.size(); 1408 pw.println(" mIconMap.size=" + N); 1409 Set<IBinder> keys = mIconMap.keySet(); 1410 int i=0; 1411 for (IBinder key: keys) { 1412 StatusBarIcon icon = mIconMap.get(key); 1413 pw.println(" [" + i + "] key=" + key); 1414 pw.println(" data=" + icon.mData); 1415 i++; 1416 } 1417 } 1418 synchronized (mNotificationData) { 1419 int N = mNotificationData.ongoingCount(); 1420 pw.println(" ongoingCount.size=" + N); 1421 for (int i=0; i<N; i++) { 1422 StatusBarNotification n = mNotificationData.getOngoing(i); 1423 pw.println(" [" + i + "] key=" + n.key + " view=" + n.view); 1424 pw.println(" data=" + n.data); 1425 } 1426 N = mNotificationData.latestCount(); 1427 pw.println(" ongoingCount.size=" + N); 1428 for (int i=0; i<N; i++) { 1429 StatusBarNotification n = mNotificationData.getLatest(i); 1430 pw.println(" [" + i + "] key=" + n.key + " view=" + n.view); 1431 pw.println(" data=" + n.data); 1432 } 1433 } 1434 synchronized (mDisableRecords) { 1435 final int N = mDisableRecords.size(); 1436 pw.println(" mDisableRecords.size=" + N 1437 + " mDisabled=0x" + Integer.toHexString(mDisabled)); 1438 for (int i=0; i<N; i++) { 1439 DisableRecord tok = mDisableRecords.get(i); 1440 pw.println(" [" + i + "] what=0x" + Integer.toHexString(tok.what) 1441 + " pkg=" + tok.pkg + " token=" + tok.token); 1442 } 1443 } 1444 1445 if (false) { 1446 pw.println("see the logcat for a dump of the views we have created."); 1447 // must happen on ui thread 1448 mHandler.post(new Runnable() { 1449 public void run() { 1450 mStatusBarView.getLocationOnScreen(mAbsPos); 1451 Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] 1452 + ") " + mStatusBarView.getWidth() + "x" 1453 + mStatusBarView.getHeight()); 1454 mStatusBarView.debug(); 1455 1456 mExpandedView.getLocationOnScreen(mAbsPos); 1457 Log.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] 1458 + ") " + mExpandedView.getWidth() + "x" 1459 + mExpandedView.getHeight()); 1460 mExpandedView.debug(); 1461 1462 mTrackingView.getLocationOnScreen(mAbsPos); 1463 Log.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] 1464 + ") " + mTrackingView.getWidth() + "x" 1465 + mTrackingView.getHeight()); 1466 mTrackingView.debug(); 1467 } 1468 }); 1469 } 1470 } 1471 1472 void onBarViewAttached() { 1473 WindowManager.LayoutParams lp; 1474 int pixelFormat; 1475 Drawable bg; 1476 1477 /// ---------- Tracking View -------------- 1478 pixelFormat = PixelFormat.TRANSLUCENT; 1479 bg = mTrackingView.getBackground(); 1480 if (bg != null) { 1481 pixelFormat = bg.getOpacity(); 1482 } 1483 1484 lp = new WindowManager.LayoutParams( 1485 ViewGroup.LayoutParams.FILL_PARENT, 1486 ViewGroup.LayoutParams.FILL_PARENT, 1487 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 1488 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1489 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 1490 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 1491 pixelFormat); 1492 // lp.token = mStatusBarView.getWindowToken(); 1493 lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; 1494 lp.setTitle("TrackingView"); 1495 mTrackingParams = lp; 1496 1497 WindowManagerImpl.getDefault().addView(mTrackingView, lp); 1498 } 1499 1500 void onTrackingViewAttached() { 1501 WindowManager.LayoutParams lp; 1502 int pixelFormat; 1503 Drawable bg; 1504 1505 /// ---------- Expanded View -------------- 1506 pixelFormat = PixelFormat.TRANSLUCENT; 1507 bg = mExpandedView.getBackground(); 1508 if (bg != null) { 1509 pixelFormat = bg.getOpacity(); 1510 if (pixelFormat != PixelFormat.TRANSLUCENT) { 1511 // we want good-looking gradients, so we force a 8-bits per 1512 // pixel format. 1513 pixelFormat = PixelFormat.RGBX_8888; 1514 } 1515 } 1516 1517 final int disph = mDisplay.getHeight(); 1518 lp = mExpandedDialog.getWindow().getAttributes(); 1519 lp.width = ViewGroup.LayoutParams.FILL_PARENT; 1520 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 1521 lp.x = 0; 1522 mTrackingPosition = lp.y = -disph; // sufficiently large negative 1523 lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; 1524 lp.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1525 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 1526 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 1527 | WindowManager.LayoutParams.FLAG_DITHER 1528 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1529 lp.format = pixelFormat; 1530 lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; 1531 lp.setTitle("StatusBarExpanded"); 1532 mExpandedDialog.getWindow().setAttributes(lp); 1533 mExpandedDialog.getWindow().setFormat(pixelFormat); 1534 mExpandedParams = lp; 1535 1536 mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); 1537 mExpandedDialog.setContentView(mExpandedView, 1538 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 1539 ViewGroup.LayoutParams.WRAP_CONTENT)); 1540 mExpandedDialog.show(); 1541 FrameLayout hack = (FrameLayout)mExpandedView.getParent(); 1542 hack.setForeground(null); 1543 } 1544 1545 void setDateViewVisibility(boolean visible, int anim) { 1546 mDateView.setUpdates(visible); 1547 mDateView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 1548 mDateView.startAnimation(loadAnim(anim, null)); 1549 } 1550 1551 void setNotificationIconVisibility(boolean visible, int anim) { 1552 int old = mNotificationIcons.getVisibility(); 1553 int v = visible ? View.VISIBLE : View.INVISIBLE; 1554 if (old != v) { 1555 mNotificationIcons.setVisibility(v); 1556 mNotificationIcons.startAnimation(loadAnim(anim, null)); 1557 } 1558 } 1559 1560 void updateExpandedViewPos(int expandedPosition) { 1561 if (SPEW) { 1562 Log.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition 1563 + " mTrackingParams.y=" + mTrackingParams.y 1564 + " mTrackingPosition=" + mTrackingPosition); 1565 } 1566 1567 int h = mStatusBarView.getHeight(); 1568 int disph = mDisplay.getHeight(); 1569 1570 // If the expanded view is not visible, make sure they're still off screen. 1571 // Maybe the view was resized. 1572 if (!mExpandedVisible) { 1573 if (mTrackingView != null) { 1574 mTrackingPosition = mTrackingParams.y = -disph; 1575 WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); 1576 } 1577 if (mExpandedParams != null) { 1578 mExpandedParams.y = -disph; 1579 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 1580 } 1581 return; 1582 } 1583 1584 // tracking view... 1585 int pos; 1586 if (expandedPosition == EXPANDED_FULL_OPEN) { 1587 pos = h; 1588 } 1589 else if (expandedPosition == EXPANDED_LEAVE_ALONE) { 1590 pos = mTrackingPosition; 1591 } 1592 else { 1593 if (expandedPosition <= disph) { 1594 pos = expandedPosition; 1595 } else { 1596 pos = disph; 1597 } 1598 pos -= disph-h; 1599 } 1600 mTrackingPosition = mTrackingParams.y = pos; 1601 mTrackingParams.height = disph-h; 1602 WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); 1603 1604 mCloseView.getLocationInWindow(mCloseLocation); 1605 1606 if (mExpandedParams != null) { 1607 mExpandedParams.y = pos + mTrackingView.getHeight() 1608 - (mTrackingParams.height-mCloseLocation[1]) - mExpandedView.getHeight(); 1609 int max = h; 1610 if (mExpandedParams.y > max) { 1611 mExpandedParams.y = max; 1612 } 1613 int min = mTrackingPosition; 1614 if (mExpandedParams.y < min) { 1615 mExpandedParams.y = min; 1616 } 1617 1618 /* 1619 Log.d(TAG, "mTrackingPosition=" + mTrackingPosition 1620 + " mTrackingView.height=" + mTrackingView.getHeight() 1621 + " diff=" + (mTrackingPosition + mTrackingView.getHeight()) 1622 + " h=" + h); 1623 */ 1624 panelSlightlyVisible((mTrackingPosition + mTrackingView.getHeight()) > h); 1625 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 1626 } 1627 1628 if (SPEW) { 1629 Log.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition 1630 + " mTrackingParams.y=" + mTrackingParams.y 1631 + " mTrackingPosition=" + mTrackingPosition 1632 + " mExpandedParams.y=" + mExpandedParams.y); 1633 } 1634 } 1635 1636 void updateAvailableHeight() { 1637 if (mExpandedView != null) { 1638 int disph = mDisplay.getHeight(); 1639 int h = mStatusBarView.getHeight(); 1640 int max = disph - (mCloseView.getHeight() + h); 1641 mExpandedView.setMaxHeight(max); 1642 } 1643 } 1644 1645 /** 1646 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 1647 * This was added last-minute and is inconsistent with the way the rest of the notifications 1648 * are handled, because the notification isn't really cancelled. The lights are just 1649 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1650 * this is what he wants. (see bug 1131461) 1651 */ 1652 private boolean mPanelSlightlyVisible; 1653 void panelSlightlyVisible(boolean visible) { 1654 if (mPanelSlightlyVisible != visible) { 1655 mPanelSlightlyVisible = visible; 1656 if (visible) { 1657 // tell the notification manager to turn off the lights. 1658 mNotificationCallbacks.onPanelRevealed(); 1659 } 1660 } 1661 } 1662 1663 void performDisableActions(int net) { 1664 int old = mDisabled; 1665 int diff = net ^ old; 1666 mDisabled = net; 1667 1668 // act accordingly 1669 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 1670 if ((net & StatusBarManager.DISABLE_EXPAND) != 0) { 1671 performCollapse(); 1672 } 1673 } 1674 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 1675 if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 1676 Log.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); 1677 if (mTicking) { 1678 mNotificationIcons.setVisibility(View.INVISIBLE); 1679 mTicker.halt(); 1680 } else { 1681 setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out); 1682 } 1683 } else { 1684 Log.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); 1685 if (!mExpandedVisible) { 1686 setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); 1687 } 1688 } 1689 } 1690 } 1691 1692 private View.OnClickListener mClearButtonListener = new View.OnClickListener() { 1693 public void onClick(View v) { 1694 mNotificationCallbacks.onClearAll(); 1695 addPendingOp(OP_EXPAND, null, false); 1696 } 1697 }; 1698 1699 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1700 public void onReceive(Context context, Intent intent) { 1701 String action = intent.getAction(); 1702 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 1703 || Intent.ACTION_SCREEN_OFF.equals(action)) { 1704 deactivate(); 1705 } 1706 else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { 1707 updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false), 1708 intent.getStringExtra(Telephony.Intents.EXTRA_SPN), 1709 intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false), 1710 intent.getStringExtra(Telephony.Intents.EXTRA_PLMN)); 1711 } 1712 else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { 1713 updateResources(); 1714 } 1715 } 1716 }; 1717 1718 void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { 1719 if (false) { 1720 Log.d(TAG, "updateNetworkName showSpn=" + showSpn + " spn=" + spn 1721 + " showPlmn=" + showPlmn + " plmn=" + plmn); 1722 } 1723 boolean something = false; 1724 if (showPlmn) { 1725 mPlmnLabel.setVisibility(View.VISIBLE); 1726 if (plmn != null) { 1727 mPlmnLabel.setText(plmn); 1728 } else { 1729 mPlmnLabel.setText(R.string.lockscreen_carrier_default); 1730 } 1731 } else { 1732 mPlmnLabel.setText(""); 1733 mPlmnLabel.setVisibility(View.GONE); 1734 } 1735 if (showSpn && spn != null) { 1736 mSpnLabel.setText(spn); 1737 mSpnLabel.setVisibility(View.VISIBLE); 1738 something = true; 1739 } else { 1740 mSpnLabel.setText(""); 1741 mSpnLabel.setVisibility(View.GONE); 1742 } 1743 } 1744 1745 /** 1746 * Reload some of our resources when the configuration changes. 1747 * 1748 * We don't reload everything when the configuration changes -- we probably 1749 * should, but getting that smooth is tough. Someday we'll fix that. In the 1750 * meantime, just update the things that we know change. 1751 */ 1752 void updateResources() { 1753 mClearButton.setText(mContext.getText(R.string.status_bar_clear_all_button)); 1754 mOngoingTitle.setText(mContext.getText(R.string.status_bar_ongoing_events_title)); 1755 mLatestTitle.setText(mContext.getText(R.string.status_bar_latest_events_title)); 1756 mNoNotificationsTitle.setText(mContext.getText(R.string.status_bar_no_notifications_title)); 1757 if (false) Log.v(TAG, "updateResources"); 1758 } 1759 1760 // 1761 // tracing 1762 // 1763 1764 void postStartTracing() { 1765 mHandler.postDelayed(mStartTracing, 3000); 1766 } 1767 1768 void vibrate() { 1769 android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService( 1770 Context.VIBRATOR_SERVICE); 1771 vib.vibrate(250); 1772 } 1773 1774 Runnable mStartTracing = new Runnable() { 1775 public void run() { 1776 vibrate(); 1777 SystemClock.sleep(250); 1778 Log.d(TAG, "startTracing"); 1779 android.os.Debug.startMethodTracing("/data/statusbar-traces/trace"); 1780 mHandler.postDelayed(mStopTracing, 10000); 1781 } 1782 }; 1783 1784 Runnable mStopTracing = new Runnable() { 1785 public void run() { 1786 android.os.Debug.stopMethodTracing(); 1787 Log.d(TAG, "stopTracing"); 1788 vibrate(); 1789 } 1790 }; 1791 1792 class UninstallReceiver extends BroadcastReceiver { 1793 public UninstallReceiver() { 1794 IntentFilter filter = new IntentFilter(); 1795 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 1796 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); 1797 filter.addDataScheme("package"); 1798 mContext.registerReceiver(this, filter); 1799 } 1800 1801 @Override 1802 public void onReceive(Context context, Intent intent) { 1803 ArrayList<StatusBarNotification> list = null; 1804 synchronized (StatusBarService.this) { 1805 Uri data = intent.getData(); 1806 if (data != null) { 1807 String pkg = data.getSchemeSpecificPart(); 1808 list = mNotificationData.notificationsForPackage(pkg); 1809 } 1810 } 1811 1812 if (list != null) { 1813 final int N = list.size(); 1814 for (int i=0; i<N; i++) { 1815 removeIcon(list.get(i).key); 1816 } 1817 } 1818 } 1819 } 1820 } 1821