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