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