BaseStatusBar.java revision 3b291870d3c78cea6ca514949591ccc32f00554e
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 17package com.android.systemui.statusbar; 18 19import java.util.ArrayList; 20 21import android.app.ActivityManagerNative; 22import android.app.KeyguardManager; 23import android.app.PendingIntent; 24import android.content.Context; 25import android.content.Intent; 26import android.content.pm.ApplicationInfo; 27import android.content.pm.PackageManager.NameNotFoundException; 28import android.graphics.Rect; 29import android.net.Uri; 30import android.os.Build; 31import android.os.Handler; 32import android.os.IBinder; 33import android.os.Message; 34import android.os.RemoteException; 35import android.os.ServiceManager; 36import android.provider.Settings; 37import android.util.Log; 38import android.util.Slog; 39import android.view.Display; 40import android.view.IWindowManager; 41import android.view.LayoutInflater; 42import android.view.MenuItem; 43import android.view.MotionEvent; 44import android.view.View; 45import android.view.ViewGroup; 46import android.view.ViewGroup.LayoutParams; 47import android.view.WindowManager; 48import android.view.WindowManagerImpl; 49import android.widget.LinearLayout; 50import android.widget.RemoteViews; 51import android.widget.PopupMenu; 52 53import com.android.internal.statusbar.IStatusBarService; 54import com.android.internal.statusbar.StatusBarIcon; 55import com.android.internal.statusbar.StatusBarIconList; 56import com.android.internal.statusbar.StatusBarNotification; 57import com.android.internal.widget.SizeAdaptiveLayout; 58import com.android.systemui.SearchPanelView; 59import com.android.systemui.SystemUI; 60import com.android.systemui.recent.RecentsPanelView; 61import com.android.systemui.recent.RecentTasksLoader; 62import com.android.systemui.recent.TaskDescription; 63import com.android.systemui.statusbar.CommandQueue; 64import com.android.systemui.statusbar.tablet.StatusBarPanel; 65 66import com.android.systemui.R; 67 68public abstract class BaseStatusBar extends SystemUI implements 69 CommandQueue.Callbacks, RecentsPanelView.OnRecentsPanelVisibilityChangedListener { 70 static final String TAG = "StatusBar"; 71 private static final boolean DEBUG = false; 72 73 protected static final int MSG_OPEN_RECENTS_PANEL = 1020; 74 protected static final int MSG_CLOSE_RECENTS_PANEL = 1021; 75 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 76 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 77 protected static final int MSG_OPEN_SEARCH_PANEL = 1024; 78 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025; 79 80 protected CommandQueue mCommandQueue; 81 protected IStatusBarService mBarService; 82 protected H mHandler = createHandler(); 83 84 // used to notify status bar for suppressing notification LED 85 protected boolean mPanelSlightlyVisible; 86 87 // Search panel 88 protected SearchPanelView mSearchPanelView; 89 90 // Recent apps 91 protected RecentsPanelView mRecentsPanel; 92 protected RecentTasksLoader mRecentTasksLoader; 93 94 // UI-specific methods 95 96 /** 97 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 98 * and add them to the window manager. 99 */ 100 protected abstract void createAndAddWindows(); 101 102 protected Display mDisplay; 103 private IWindowManager mWindowManager; 104 105 106 public IWindowManager getWindowManager() { 107 return mWindowManager; 108 } 109 110 public Display getDisplay() { 111 return mDisplay; 112 } 113 114 public IStatusBarService getStatusBarService() { 115 return mBarService; 116 } 117 118 public void start() { 119 mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) 120 .getDefaultDisplay(); 121 122 mWindowManager = IWindowManager.Stub.asInterface( 123 ServiceManager.getService(Context.WINDOW_SERVICE)); 124 125 mBarService = IStatusBarService.Stub.asInterface( 126 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 127 128 // Connect in to the status bar manager service 129 StatusBarIconList iconList = new StatusBarIconList(); 130 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); 131 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 132 mCommandQueue = new CommandQueue(this, iconList); 133 134 int[] switches = new int[7]; 135 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 136 try { 137 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, 138 switches, binders); 139 } catch (RemoteException ex) { 140 // If the system process isn't there we're doomed anyway. 141 } 142 143 createAndAddWindows(); 144 145 disable(switches[0]); 146 setSystemUiVisibility(switches[1], 0xffffffff); 147 topAppWindowChanged(switches[2] != 0); 148 // StatusBarManagerService has a back up of IME token and it's restored here. 149 setImeWindowStatus(binders.get(0), switches[3], switches[4]); 150 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 151 152 // Set up the initial icon state 153 int N = iconList.size(); 154 int viewIndex = 0; 155 for (int i=0; i<N; i++) { 156 StatusBarIcon icon = iconList.getIcon(i); 157 if (icon != null) { 158 addIcon(iconList.getSlot(i), i, viewIndex, icon); 159 viewIndex++; 160 } 161 } 162 163 // Set up the initial notification state 164 N = notificationKeys.size(); 165 if (N == notifications.size()) { 166 for (int i=0; i<N; i++) { 167 addNotification(notificationKeys.get(i), notifications.get(i)); 168 } 169 } else { 170 Log.wtf(TAG, "Notification list length mismatch: keys=" + N 171 + " notifications=" + notifications.size()); 172 } 173 174 if (DEBUG) { 175 Slog.d(TAG, String.format( 176 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 177 iconList.size(), 178 switches[0], 179 switches[1], 180 switches[2], 181 switches[3] 182 )); 183 } 184 } 185 186 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 187 View vetoButton = row.findViewById(R.id.veto); 188 if (n.isClearable()) { 189 final String _pkg = n.pkg; 190 final String _tag = n.tag; 191 final int _id = n.id; 192 vetoButton.setOnClickListener(new View.OnClickListener() { 193 public void onClick(View v) { 194 try { 195 mBarService.onNotificationClear(_pkg, _tag, _id); 196 } catch (RemoteException ex) { 197 // system process is dead if we're here. 198 } 199 } 200 }); 201 vetoButton.setVisibility(View.VISIBLE); 202 } else { 203 vetoButton.setVisibility(View.GONE); 204 } 205 return vetoButton; 206 } 207 208 209 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 210 if (sbn.notification.contentView.getLayoutId() != 211 com.android.internal.R.layout.notification_template_base) { 212 int version = 0; 213 try { 214 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); 215 version = info.targetSdkVersion; 216 } catch (NameNotFoundException ex) { 217 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); 218 } 219 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 220 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 221 } else { 222 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg); 223 } 224 } 225 } 226 227 private void startApplicationDetailsActivity(String packageName) { 228 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 229 Uri.fromParts("package", packageName, null)); 230 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 231 mContext.startActivity(intent); 232 } 233 234 protected View.OnLongClickListener getNotificationLongClicker() { 235 return new View.OnLongClickListener() { 236 @Override 237 public boolean onLongClick(View v) { 238 final String packageNameF = (String) v.getTag(); 239 if (packageNameF == null) return false; 240 PopupMenu popup = new PopupMenu(mContext, v); 241 popup.getMenuInflater().inflate(R.menu.notification_popup_menu, popup.getMenu()); 242 popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 243 public boolean onMenuItemClick(MenuItem item) { 244 if (item.getItemId() == R.id.notification_inspect_item) { 245 startApplicationDetailsActivity(packageNameF); 246 animateCollapse(); 247 } else { 248 return false; 249 } 250 return true; 251 } 252 }); 253 popup.show(); 254 255 return true; 256 } 257 }; 258 } 259 260 public void dismissIntruder() { 261 // pass 262 } 263 264 @Override 265 public void toggleRecentApps() { 266 int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) 267 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; 268 mHandler.removeMessages(msg); 269 mHandler.sendEmptyMessage(msg); 270 } 271 272 @Override 273 public void preloadRecentApps() { 274 int msg = MSG_PRELOAD_RECENT_APPS; 275 mHandler.removeMessages(msg); 276 mHandler.sendEmptyMessage(msg); 277 } 278 279 @Override 280 public void cancelPreloadRecentApps() { 281 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 282 mHandler.removeMessages(msg); 283 mHandler.sendEmptyMessage(msg); 284 } 285 286 @Override 287 public void showSearchPanel() { 288 int msg = MSG_OPEN_SEARCH_PANEL; 289 mHandler.removeMessages(msg); 290 mHandler.sendEmptyMessage(msg); 291 } 292 293 @Override 294 public void hideSearchPanel() { 295 int msg = MSG_CLOSE_SEARCH_PANEL; 296 mHandler.removeMessages(msg); 297 mHandler.sendEmptyMessage(msg); 298 } 299 300 @Override 301 public void onRecentsPanelVisibilityChanged(boolean visible) { 302 } 303 304 protected abstract WindowManager.LayoutParams getRecentsLayoutParams( 305 LayoutParams layoutParams); 306 307 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 308 LayoutParams layoutParams); 309 310 protected void updateRecentsPanel(int recentsResId) { 311 // Recents Panel 312 boolean visible = false; 313 ArrayList<TaskDescription> recentTasksList = null; 314 boolean firstScreenful = false; 315 if (mRecentsPanel != null) { 316 visible = mRecentsPanel.isShowing(); 317 WindowManagerImpl.getDefault().removeView(mRecentsPanel); 318 if (visible) { 319 recentTasksList = mRecentsPanel.getRecentTasksList(); 320 firstScreenful = mRecentsPanel.getFirstScreenful(); 321 } 322 } 323 324 // Provide RecentsPanelView with a temporary parent to allow layout params to work. 325 LinearLayout tmpRoot = new LinearLayout(mContext); 326 mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate( 327 recentsResId, tmpRoot, false); 328 mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader); 329 mRecentTasksLoader.setRecentsPanel(mRecentsPanel); 330 mRecentsPanel.setOnTouchListener( 331 new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, mRecentsPanel)); 332 mRecentsPanel.setVisibility(View.GONE); 333 334 335 WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams()); 336 337 WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); 338 mRecentsPanel.setBar(this); 339 if (visible) { 340 mRecentsPanel.show(true, false, recentTasksList, firstScreenful); 341 } 342 343 } 344 345 protected void updateSearchPanel() { 346 // Search Panel 347 boolean visible = false; 348 if (mSearchPanelView != null) { 349 visible = mSearchPanelView.isShowing(); 350 WindowManagerImpl.getDefault().removeView(mSearchPanelView); 351 } 352 353 // Provide SearchPanel with a temporary parent to allow layout params to work. 354 LinearLayout tmpRoot = new LinearLayout(mContext); 355 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 356 R.layout.status_bar_search_panel, tmpRoot, false); 357 mSearchPanelView.setOnTouchListener( 358 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 359 mSearchPanelView.setVisibility(View.GONE); 360 361 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 362 363 WindowManagerImpl.getDefault().addView(mSearchPanelView, lp); 364 mSearchPanelView.setBar(this); 365 if (visible) { 366 mSearchPanelView.show(true, false); 367 } 368 } 369 370 protected H createHandler() { 371 return new H(); 372 } 373 374 protected class H extends Handler { 375 public void handleMessage(Message m) { 376 switch (m.what) { 377 case MSG_OPEN_RECENTS_PANEL: 378 if (DEBUG) Slog.d(TAG, "opening recents panel"); 379 if (mRecentsPanel != null) { 380 mRecentsPanel.show(true, true); 381 } 382 break; 383 case MSG_CLOSE_RECENTS_PANEL: 384 if (DEBUG) Slog.d(TAG, "closing recents panel"); 385 if (mRecentsPanel != null && mRecentsPanel.isShowing()) { 386 mRecentsPanel.show(false, true); 387 } 388 break; 389 case MSG_PRELOAD_RECENT_APPS: 390 if (DEBUG) Slog.d(TAG, "preloading recents"); 391 mRecentsPanel.preloadRecentTasksList(); 392 break; 393 case MSG_CANCEL_PRELOAD_RECENT_APPS: 394 if (DEBUG) Slog.d(TAG, "cancel preloading recents"); 395 mRecentsPanel.clearRecentTasksList(); 396 break; 397 case MSG_OPEN_SEARCH_PANEL: 398 if (DEBUG) Slog.d(TAG, "opening search panel"); 399 if (mSearchPanelView != null && mSearchPanelView.isSearchAvailable()) { 400 mSearchPanelView.show(true, true); 401 } 402 break; 403 case MSG_CLOSE_SEARCH_PANEL: 404 if (DEBUG) Slog.d(TAG, "closing search panel"); 405 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 406 mSearchPanelView.show(false, true); 407 } 408 break; 409 } 410 } 411 } 412 413 public class TouchOutsideListener implements View.OnTouchListener { 414 private int mMsg; 415 private StatusBarPanel mPanel; 416 417 public TouchOutsideListener(int msg, StatusBarPanel panel) { 418 mMsg = msg; 419 mPanel = panel; 420 } 421 422 public boolean onTouch(View v, MotionEvent ev) { 423 final int action = ev.getAction(); 424 if (action == MotionEvent.ACTION_OUTSIDE 425 || (action == MotionEvent.ACTION_DOWN 426 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 427 mHandler.removeMessages(mMsg); 428 mHandler.sendEmptyMessage(mMsg); 429 return true; 430 } 431 return false; 432 } 433 } 434 435 protected void workAroundBadLayerDrawableOpacity(View v) { 436 } 437 438 protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 439 int minHeight = 440 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 441 int maxHeight = 442 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 443 StatusBarNotification sbn = entry.notification; 444 RemoteViews oneU = sbn.notification.contentView; 445 RemoteViews large = sbn.notification.bigContentView; 446 if (oneU == null) { 447 return false; 448 } 449 450 // create the row view 451 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 452 Context.LAYOUT_INFLATER_SERVICE); 453 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 454 455 // for blaming (see SwipeHelper.setLongPressListener) 456 row.setTag(sbn.pkg); 457 458 // XXX: temporary: while testing big notifications, auto-expand all of them 459 ViewGroup.LayoutParams lp = row.getLayoutParams(); 460 Boolean expandable = Boolean.FALSE; 461 if (large != null) { 462 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 463 expandable = Boolean.TRUE; 464 } else { 465 lp.height = minHeight; 466 } 467 row.setLayoutParams(lp); 468 row.setTag(R.id.expandable_tag, expandable); 469 workAroundBadLayerDrawableOpacity(row); 470 View vetoButton = updateNotificationVetoButton(row, sbn); 471 vetoButton.setContentDescription(mContext.getString( 472 R.string.accessibility_remove_notification)); 473 474 // NB: the large icon is now handled entirely by the template 475 476 // bind the click event to the content area 477 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 478 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive); 479 480 // Ensure that R.id.content is properly set to 64dp high if 1U 481 lp = content.getLayoutParams(); 482 if (large == null) { 483 lp.height = minHeight; 484 } 485 content.setLayoutParams(lp); 486 487 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 488 489 PendingIntent contentIntent = sbn.notification.contentIntent; 490 if (contentIntent != null) { 491 final View.OnClickListener listener = new NotificationClicker(contentIntent, 492 sbn.pkg, sbn.tag, sbn.id); 493 content.setOnClickListener(listener); 494 } else { 495 content.setOnClickListener(null); 496 } 497 498 View expandedOneU = null; 499 View expandedLarge = null; 500 Exception exception = null; 501 try { 502 expandedOneU = oneU.apply(mContext, adaptive); 503 if (large != null) { 504 expandedLarge = large.apply(mContext, adaptive); 505 } 506 } 507 catch (RuntimeException e) { 508 exception = e; 509 } 510 if (expandedOneU == null && expandedLarge == null) { 511 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 512 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 513 return false; 514 } else { 515 if (expandedOneU != null) { 516 SizeAdaptiveLayout.LayoutParams params = 517 new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams()); 518 params.minHeight = minHeight; 519 params.maxHeight = minHeight; 520 adaptive.addView(expandedOneU, params); 521 } 522 if (expandedLarge != null) { 523 SizeAdaptiveLayout.LayoutParams params = 524 new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams()); 525 params.minHeight = minHeight+1; 526 params.maxHeight = SizeAdaptiveLayout.LayoutParams.UNBOUNDED; 527 adaptive.addView(expandedLarge, params); 528 } 529 row.setDrawingCacheEnabled(true); 530 } 531 532 applyLegacyRowBackground(sbn, content); 533 534 entry.row = row; 535 entry.content = content; 536 entry.expanded = expandedOneU; 537 entry.expandedLarge = expandedOneU; 538 539 return true; 540 } 541 542 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 543 return new NotificationClicker(intent, pkg, tag, id); 544 } 545 546 private class NotificationClicker implements View.OnClickListener { 547 private PendingIntent mIntent; 548 private String mPkg; 549 private String mTag; 550 private int mId; 551 552 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 553 mIntent = intent; 554 mPkg = pkg; 555 mTag = tag; 556 mId = id; 557 } 558 559 public void onClick(View v) { 560 try { 561 // The intent we are sending is for the application, which 562 // won't have permission to immediately start an activity after 563 // the user switches to home. We know it is safe to do at this 564 // point, so make sure new activity switches are now allowed. 565 ActivityManagerNative.getDefault().resumeAppSwitches(); 566 // Also, notifications can be launched from the lock screen, 567 // so dismiss the lock screen when the activity starts. 568 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 569 } catch (RemoteException e) { 570 } 571 572 if (mIntent != null) { 573 int[] pos = new int[2]; 574 v.getLocationOnScreen(pos); 575 Intent overlay = new Intent(); 576 overlay.setSourceBounds( 577 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 578 try { 579 mIntent.send(mContext, 0, overlay); 580 } catch (PendingIntent.CanceledException e) { 581 // the stack trace isn't very helpful here. Just log the exception message. 582 Slog.w(TAG, "Sending contentIntent failed: " + e); 583 } 584 585 KeyguardManager kgm = 586 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 587 if (kgm != null) kgm.exitKeyguardSecurely(null); 588 } 589 590 try { 591 mBarService.onNotificationClick(mPkg, mTag, mId); 592 } catch (RemoteException ex) { 593 // system process is dead if we're here. 594 } 595 596 // close the shade if it was open 597 animateCollapse(); 598 visibilityChanged(false); 599 600 // If this click was on the intruder alert, hide that instead 601// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 602 } 603 } 604 /** 605 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 606 * This was added last-minute and is inconsistent with the way the rest of the notifications 607 * are handled, because the notification isn't really cancelled. The lights are just 608 * turned off. If any other notifications happen, the lights will turn back on. Steve says 609 * this is what he wants. (see bug 1131461) 610 */ 611 protected void visibilityChanged(boolean visible) { 612 if (mPanelSlightlyVisible != visible) { 613 mPanelSlightlyVisible = visible; 614 try { 615 mBarService.onPanelRevealed(); 616 } catch (RemoteException ex) { 617 // Won't fail unless the world has ended. 618 } 619 } 620 } 621 622} 623