/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
https://github.com/aizuzi/platform_frameworks_base · Java · 1104 lines · 868 code · 138 blank · 98 comment · 215 complexity · 91e365098d7f41352ae7ce4488bcc562 MD5 · raw file
- /*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.android.systemui.statusbar;
- import android.app.ActivityManager;
- import android.app.ActivityManagerNative;
- import android.app.Notification;
- import android.app.PendingIntent;
- import android.app.TaskStackBuilder;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.content.pm.ApplicationInfo;
- import android.content.pm.PackageManager.NameNotFoundException;
- import android.content.res.Configuration;
- import android.database.ContentObserver;
- import android.graphics.Rect;
- import android.net.Uri;
- import android.os.Build;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.os.PowerManager;
- import android.os.RemoteException;
- import android.os.ServiceManager;
- import android.os.UserHandle;
- import android.provider.Settings;
- import android.service.dreams.DreamService;
- import android.service.dreams.IDreamManager;
- import android.service.notification.StatusBarNotification;
- import android.text.TextUtils;
- import android.util.Log;
- import android.view.Display;
- import android.view.IWindowManager;
- import android.view.LayoutInflater;
- import android.view.MenuItem;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.ViewGroup;
- import android.view.ViewGroup.LayoutParams;
- import android.view.WindowManager;
- import android.view.WindowManagerGlobal;
- import android.widget.ImageView;
- import android.widget.LinearLayout;
- import android.widget.PopupMenu;
- import android.widget.RemoteViews;
- import android.widget.TextView;
- import com.android.internal.statusbar.IStatusBarService;
- import com.android.internal.statusbar.StatusBarIcon;
- import com.android.internal.statusbar.StatusBarIconList;
- import com.android.internal.widget.SizeAdaptiveLayout;
- import com.android.systemui.R;
- import com.android.systemui.RecentsComponent;
- import com.android.systemui.SearchPanelView;
- import com.android.systemui.SystemUI;
- import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
- import com.android.systemui.statusbar.policy.NotificationRowLayout;
- import java.util.ArrayList;
- import java.util.Locale;
- public abstract class BaseStatusBar extends SystemUI implements
- CommandQueue.Callbacks {
- public static final String TAG = "StatusBar";
- public static final boolean DEBUG = false;
- public static final boolean MULTIUSER_DEBUG = false;
- protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
- protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
- protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
- protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
- protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
- protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
- protected static final int MSG_SHOW_HEADS_UP = 1026;
- protected static final int MSG_HIDE_HEADS_UP = 1027;
- protected static final int MSG_ESCALATE_HEADS_UP = 1028;
- protected static final boolean ENABLE_HEADS_UP = true;
- // scores above this threshold should be displayed in heads up mode.
- protected static final int INTERRUPTION_THRESHOLD = 11;
- protected static final String SETTING_HEADS_UP = "heads_up_enabled";
- // Should match the value in PhoneWindowManager
- public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
- public static final int EXPANDED_LEAVE_ALONE = -10000;
- public static final int EXPANDED_FULL_OPEN = -10001;
- protected CommandQueue mCommandQueue;
- protected IStatusBarService mBarService;
- protected H mHandler = createHandler();
- // all notifications
- protected NotificationData mNotificationData = new NotificationData();
- protected NotificationRowLayout mPile;
- protected NotificationData.Entry mInterruptingNotificationEntry;
- protected long mInterruptingNotificationTime;
- // used to notify status bar for suppressing notification LED
- protected boolean mPanelSlightlyVisible;
- // Search panel
- protected SearchPanelView mSearchPanelView;
- protected PopupMenu mNotificationBlamePopup;
- protected int mCurrentUserId = 0;
- protected int mLayoutDirection = -1; // invalid
- private Locale mLocale;
- protected boolean mUseHeadsUp = false;
- protected IDreamManager mDreamManager;
- PowerManager mPowerManager;
- protected int mRowHeight;
- // UI-specific methods
- /**
- * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
- * and add them to the window manager.
- */
- protected abstract void createAndAddWindows();
- protected WindowManager mWindowManager;
- protected IWindowManager mWindowManagerService;
- protected abstract void refreshLayout(int layoutDirection);
- protected Display mDisplay;
- private boolean mDeviceProvisioned = false;
- private RecentsComponent mRecents;
- public IStatusBarService getStatusBarService() {
- return mBarService;
- }
- public boolean isDeviceProvisioned() {
- return mDeviceProvisioned;
- }
- private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- final boolean provisioned = 0 != Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
- if (provisioned != mDeviceProvisioned) {
- mDeviceProvisioned = provisioned;
- updateNotificationIcons();
- }
- }
- };
- private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
- @Override
- public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
- if (DEBUG) {
- Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
- }
- final boolean isActivity = pendingIntent.isActivity();
- if (isActivity) {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManagerNative.getDefault().resumeAppSwitches();
- // Also, notifications can be launched from the lock screen,
- // so dismiss the lock screen when the activity starts.
- ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
- } catch (RemoteException e) {
- }
- }
- boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
- if (isActivity && handled) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- visibilityChanged(false);
- }
- return handled;
- }
- };
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
- userSwitched(mCurrentUserId);
- }
- }
- };
- public void start() {
- mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
- mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
- mDisplay = mWindowManager.getDefaultDisplay();
- mDreamManager = IDreamManager.Stub.asInterface(
- ServiceManager.checkService(DreamService.DREAM_SERVICE));
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mProvisioningObserver.onChange(false); // set up
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
- mProvisioningObserver);
- mBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- mRecents = getComponent(RecentsComponent.class);
- mLocale = mContext.getResources().getConfiguration().locale;
- mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
- // Connect in to the status bar manager service
- StatusBarIconList iconList = new StatusBarIconList();
- ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
- ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
- mCommandQueue = new CommandQueue(this, iconList);
- int[] switches = new int[7];
- ArrayList<IBinder> binders = new ArrayList<IBinder>();
- try {
- mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
- switches, binders);
- } catch (RemoteException ex) {
- // If the system process isn't there we're doomed anyway.
- }
- createAndAddWindows();
- disable(switches[0]);
- setSystemUiVisibility(switches[1], 0xffffffff);
- topAppWindowChanged(switches[2] != 0);
- // StatusBarManagerService has a back up of IME token and it's restored here.
- setImeWindowStatus(binders.get(0), switches[3], switches[4]);
- setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
- // Set up the initial icon state
- int N = iconList.size();
- int viewIndex = 0;
- for (int i=0; i<N; i++) {
- StatusBarIcon icon = iconList.getIcon(i);
- if (icon != null) {
- addIcon(iconList.getSlot(i), i, viewIndex, icon);
- viewIndex++;
- }
- }
- // Set up the initial notification state
- N = notificationKeys.size();
- if (N == notifications.size()) {
- for (int i=0; i<N; i++) {
- addNotification(notificationKeys.get(i), notifications.get(i));
- }
- } else {
- Log.wtf(TAG, "Notification list length mismatch: keys=" + N
- + " notifications=" + notifications.size());
- }
- if (DEBUG) {
- Log.d(TAG, String.format(
- "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
- iconList.size(),
- switches[0],
- switches[1],
- switches[2],
- switches[3]
- ));
- }
- mCurrentUserId = ActivityManager.getCurrentUser();
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(mBroadcastReceiver, filter);
- }
- public void userSwitched(int newUserId) {
- // should be overridden
- }
- public boolean notificationIsForCurrentUser(StatusBarNotification n) {
- final int thisUserId = mCurrentUserId;
- final int notificationUserId = n.getUserId();
- if (DEBUG && MULTIUSER_DEBUG) {
- Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
- n, thisUserId, notificationUserId));
- }
- return notificationUserId == UserHandle.USER_ALL
- || thisUserId == notificationUserId;
- }
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- final Locale locale = mContext.getResources().getConfiguration().locale;
- final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
- if (! locale.equals(mLocale) || ld != mLayoutDirection) {
- if (DEBUG) {
- Log.v(TAG, String.format(
- "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
- locale, ld));
- }
- mLocale = locale;
- mLayoutDirection = ld;
- refreshLayout(ld);
- }
- }
- protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
- View vetoButton = row.findViewById(R.id.veto);
- if (n.isClearable() || (mInterruptingNotificationEntry != null
- && mInterruptingNotificationEntry.row == row)) {
- final String _pkg = n.getPackageName();
- final String _tag = n.getTag();
- final int _id = n.getId();
- vetoButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- // Accessibility feedback
- v.announceForAccessibility(
- mContext.getString(R.string.accessibility_notification_dismissed));
- try {
- mBarService.onNotificationClear(_pkg, _tag, _id);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- }
- });
- vetoButton.setVisibility(View.VISIBLE);
- } else {
- vetoButton.setVisibility(View.GONE);
- }
- vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- return vetoButton;
- }
- protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
- if (sbn.getNotification().contentView.getLayoutId() !=
- com.android.internal.R.layout.notification_template_base) {
- int version = 0;
- try {
- ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
- version = info.targetSdkVersion;
- } catch (NameNotFoundException ex) {
- Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
- }
- if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
- content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
- } else {
- content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
- }
- }
- }
- private void startApplicationDetailsActivity(String packageName) {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.fromParts("package", packageName, null));
- intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
- TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
- null, UserHandle.CURRENT);
- }
- protected View.OnLongClickListener getNotificationLongClicker() {
- return new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- final String packageNameF = (String) v.getTag();
- if (packageNameF == null) return false;
- if (v.getWindowToken() == null) return false;
- mNotificationBlamePopup = new PopupMenu(mContext, v);
- mNotificationBlamePopup.getMenuInflater().inflate(
- R.menu.notification_popup_menu,
- mNotificationBlamePopup.getMenu());
- mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
- public boolean onMenuItemClick(MenuItem item) {
- if (item.getItemId() == R.id.notification_inspect_item) {
- startApplicationDetailsActivity(packageNameF);
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- } else {
- return false;
- }
- return true;
- }
- });
- mNotificationBlamePopup.show();
- return true;
- }
- };
- }
- public void dismissPopups() {
- if (mNotificationBlamePopup != null) {
- mNotificationBlamePopup.dismiss();
- mNotificationBlamePopup = null;
- }
- }
- public void onHeadsUpDismissed() {
- }
- @Override
- public void toggleRecentApps() {
- int msg = MSG_TOGGLE_RECENTS_PANEL;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
- @Override
- public void preloadRecentApps() {
- int msg = MSG_PRELOAD_RECENT_APPS;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
- @Override
- public void cancelPreloadRecentApps() {
- int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
- @Override
- public void showSearchPanel() {
- int msg = MSG_OPEN_SEARCH_PANEL;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
- @Override
- public void hideSearchPanel() {
- int msg = MSG_CLOSE_SEARCH_PANEL;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
- protected abstract WindowManager.LayoutParams getSearchLayoutParams(
- LayoutParams layoutParams);
- protected void updateSearchPanel() {
- // Search Panel
- boolean visible = false;
- if (mSearchPanelView != null) {
- visible = mSearchPanelView.isShowing();
- mWindowManager.removeView(mSearchPanelView);
- }
- // Provide SearchPanel with a temporary parent to allow layout params to work.
- LinearLayout tmpRoot = new LinearLayout(mContext);
- mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
- R.layout.status_bar_search_panel, tmpRoot, false);
- mSearchPanelView.setOnTouchListener(
- new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
- mSearchPanelView.setVisibility(View.GONE);
- WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
- mWindowManager.addView(mSearchPanelView, lp);
- mSearchPanelView.setBar(this);
- if (visible) {
- mSearchPanelView.show(true, false);
- }
- }
- protected H createHandler() {
- return new H();
- }
- static void sendCloseSystemWindows(Context context, String reason) {
- if (ActivityManagerNative.isSystemReady()) {
- try {
- ActivityManagerNative.getDefault().closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- }
- }
- protected abstract View getStatusBarView();
- protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
- // additional optimization when we have software system buttons - start loading the recent
- // tasks on touch down
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- int action = event.getAction() & MotionEvent.ACTION_MASK;
- if (action == MotionEvent.ACTION_DOWN) {
- preloadRecentTasksList();
- } else if (action == MotionEvent.ACTION_CANCEL) {
- cancelPreloadingRecentTasksList();
- } else if (action == MotionEvent.ACTION_UP) {
- if (!v.isPressed()) {
- cancelPreloadingRecentTasksList();
- }
- }
- return false;
- }
- };
- protected void toggleRecentsActivity() {
- if (mRecents != null) {
- mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
- }
- }
- protected void preloadRecentTasksList() {
- if (mRecents != null) {
- mRecents.preloadRecentTasksList();
- }
- }
- protected void cancelPreloadingRecentTasksList() {
- if (mRecents != null) {
- mRecents.cancelPreloadingRecentTasksList();
- }
- }
- protected void closeRecents() {
- if (mRecents != null) {
- mRecents.closeRecents();
- }
- }
- public abstract void resetHeadsUpDecayTimer();
- protected class H extends Handler {
- public void handleMessage(Message m) {
- Intent intent;
- switch (m.what) {
- case MSG_TOGGLE_RECENTS_PANEL:
- toggleRecentsActivity();
- break;
- case MSG_CLOSE_RECENTS_PANEL:
- closeRecents();
- break;
- case MSG_PRELOAD_RECENT_APPS:
- preloadRecentTasksList();
- break;
- case MSG_CANCEL_PRELOAD_RECENT_APPS:
- cancelPreloadingRecentTasksList();
- break;
- case MSG_OPEN_SEARCH_PANEL:
- if (DEBUG) Log.d(TAG, "opening search panel");
- if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
- mSearchPanelView.show(true, true);
- onShowSearchPanel();
- }
- break;
- case MSG_CLOSE_SEARCH_PANEL:
- if (DEBUG) Log.d(TAG, "closing search panel");
- if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
- mSearchPanelView.show(false, true);
- onHideSearchPanel();
- }
- break;
- }
- }
- }
- public class TouchOutsideListener implements View.OnTouchListener {
- private int mMsg;
- private StatusBarPanel mPanel;
- public TouchOutsideListener(int msg, StatusBarPanel panel) {
- mMsg = msg;
- mPanel = panel;
- }
- public boolean onTouch(View v, MotionEvent ev) {
- final int action = ev.getAction();
- if (action == MotionEvent.ACTION_OUTSIDE
- || (action == MotionEvent.ACTION_DOWN
- && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
- mHandler.removeMessages(mMsg);
- mHandler.sendEmptyMessage(mMsg);
- return true;
- }
- return false;
- }
- }
- protected void workAroundBadLayerDrawableOpacity(View v) {
- }
- protected void onHideSearchPanel() {
- }
- protected void onShowSearchPanel() {
- }
- public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
- int minHeight =
- mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
- int maxHeight =
- mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
- StatusBarNotification sbn = entry.notification;
- RemoteViews contentView = sbn.getNotification().contentView;
- RemoteViews bigContentView = sbn.getNotification().bigContentView;
- if (contentView == null) {
- return false;
- }
- // create the row view
- LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
- R.layout.status_bar_notification_row, parent, false);
- // for blaming (see SwipeHelper.setLongPressListener)
- row.setTag(sbn.getPackageName());
- workAroundBadLayerDrawableOpacity(row);
- View vetoButton = updateNotificationVetoButton(row, sbn);
- vetoButton.setContentDescription(mContext.getString(
- R.string.accessibility_remove_notification));
- // NB: the large icon is now handled entirely by the template
- // bind the click event to the content area
- ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
- ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
- content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- PendingIntent contentIntent = sbn.getNotification().contentIntent;
- if (contentIntent != null) {
- final View.OnClickListener listener = new NotificationClicker(contentIntent,
- sbn.getPackageName(), sbn.getTag(), sbn.getId());
- content.setOnClickListener(listener);
- } else {
- content.setOnClickListener(null);
- }
- View contentViewLocal = null;
- View bigContentViewLocal = null;
- try {
- contentViewLocal = contentView.apply(mContext, adaptive, mOnClickHandler);
- if (bigContentView != null) {
- bigContentViewLocal = bigContentView.apply(mContext, adaptive, mOnClickHandler);
- }
- }
- catch (RuntimeException e) {
- final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
- Log.e(TAG, "couldn't inflate view for notification " + ident, e);
- return false;
- }
- if (contentViewLocal != null) {
- SizeAdaptiveLayout.LayoutParams params =
- new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
- params.minHeight = minHeight;
- params.maxHeight = minHeight;
- adaptive.addView(contentViewLocal, params);
- }
- if (bigContentViewLocal != null) {
- SizeAdaptiveLayout.LayoutParams params =
- new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
- params.minHeight = minHeight+1;
- params.maxHeight = maxHeight;
- adaptive.addView(bigContentViewLocal, params);
- }
- row.setDrawingCacheEnabled(true);
- applyLegacyRowBackground(sbn, content);
- if (MULTIUSER_DEBUG) {
- TextView debug = (TextView) row.findViewById(R.id.debug_info);
- if (debug != null) {
- debug.setVisibility(View.VISIBLE);
- debug.setText("U " + entry.notification.getUserId());
- }
- }
- entry.row = row;
- entry.row.setRowHeight(mRowHeight);
- entry.content = content;
- entry.expanded = contentViewLocal;
- entry.setBigContentView(bigContentViewLocal);
- return true;
- }
- public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
- return new NotificationClicker(intent, pkg, tag, id);
- }
- protected class NotificationClicker implements View.OnClickListener {
- private PendingIntent mIntent;
- private String mPkg;
- private String mTag;
- private int mId;
- public NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
- mIntent = intent;
- mPkg = pkg;
- mTag = tag;
- mId = id;
- }
- public void onClick(View v) {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManagerNative.getDefault().resumeAppSwitches();
- // Also, notifications can be launched from the lock screen,
- // so dismiss the lock screen when the activity starts.
- ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
- } catch (RemoteException e) {
- }
- if (mIntent != null) {
- int[] pos = new int[2];
- v.getLocationOnScreen(pos);
- Intent overlay = new Intent();
- overlay.setSourceBounds(
- new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
- try {
- mIntent.send(mContext, 0, overlay);
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here. Just log the exception message.
- Log.w(TAG, "Sending contentIntent failed: " + e);
- }
- KeyguardTouchDelegate.getInstance(mContext).dismiss();
- }
- try {
- mBarService.onNotificationClick(mPkg, mTag, mId);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- visibilityChanged(false);
- }
- }
- /**
- * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
- * This was added last-minute and is inconsistent with the way the rest of the notifications
- * are handled, because the notification isn't really cancelled. The lights are just
- * turned off. If any other notifications happen, the lights will turn back on. Steve says
- * this is what he wants. (see bug 1131461)
- */
- protected void visibilityChanged(boolean visible) {
- if (mPanelSlightlyVisible != visible) {
- mPanelSlightlyVisible = visible;
- try {
- mBarService.onPanelRevealed();
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- }
- }
- /**
- * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
- * about the failure.
- *
- * WARNING: this will call back into us. Don't hold any locks.
- */
- void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
- removeNotification(key);
- try {
- mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message);
- } catch (RemoteException ex) {
- // The end is nigh.
- }
- }
- protected StatusBarNotification removeNotificationViews(IBinder key) {
- NotificationData.Entry entry = mNotificationData.remove(key);
- if (entry == null) {
- Log.w(TAG, "removeNotification for unknown key: " + key);
- return null;
- }
- // Remove the expanded view.
- ViewGroup rowParent = (ViewGroup)entry.row.getParent();
- if (rowParent != null) rowParent.removeView(entry.row);
- updateExpansionStates();
- updateNotificationIcons();
- return entry.notification;
- }
- protected NotificationData.Entry createNotificationViews(IBinder key,
- StatusBarNotification notification) {
- if (DEBUG) {
- Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification);
- }
- // Construct the icon.
- final StatusBarIconView iconView = new StatusBarIconView(mContext,
- notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()),
- notification.getNotification());
- iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
- notification.getUser(),
- notification.getNotification().icon,
- notification.getNotification().iconLevel,
- notification.getNotification().number,
- notification.getNotification().tickerText);
- if (!iconView.set(ic)) {
- handleNotificationError(key, notification, "Couldn't create icon: " + ic);
- return null;
- }
- // Construct the expanded view.
- NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
- if (!inflateViews(entry, mPile)) {
- handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
- + notification);
- return null;
- }
- return entry;
- }
- protected void addNotificationViews(NotificationData.Entry entry) {
- if (entry == null) {
- return;
- }
- // Add the expanded view and icon.
- int pos = mNotificationData.add(entry);
- if (DEBUG) {
- Log.d(TAG, "addNotificationViews: added at " + pos);
- }
- updateExpansionStates();
- updateNotificationIcons();
- }
- private void addNotificationViews(IBinder key, StatusBarNotification notification) {
- addNotificationViews(createNotificationViews(key, notification));
- }
- protected void updateExpansionStates() {
- int N = mNotificationData.size();
- for (int i = 0; i < N; i++) {
- NotificationData.Entry entry = mNotificationData.get(i);
- if (!entry.row.isUserLocked()) {
- if (i == (N-1)) {
- if (DEBUG) Log.d(TAG, "expanding top notification at " + i);
- entry.row.setExpanded(true);
- } else {
- if (!entry.row.isUserExpanded()) {
- if (DEBUG) Log.d(TAG, "collapsing notification at " + i);
- entry.row.setExpanded(false);
- } else {
- if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i);
- }
- }
- } else {
- if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i);
- }
- }
- }
- protected abstract void haltTicker();
- protected abstract void setAreThereNotifications();
- protected abstract void updateNotificationIcons();
- protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
- protected abstract void updateExpandedViewPos(int expandedPosition);
- protected abstract int getExpandedViewMaxHeight();
- protected abstract boolean shouldDisableNavbarGestures();
- protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
- return parent != null && parent.indexOfChild(entry.row) == 0;
- }
- public void updateNotification(IBinder key, StatusBarNotification notification) {
- if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
- final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
- if (oldEntry == null) {
- Log.w(TAG, "updateNotification for unknown key: " + key);
- return;
- }
- final StatusBarNotification oldNotification = oldEntry.notification;
- // XXX: modify when we do something more intelligent with the two content views
- final RemoteViews oldContentView = oldNotification.getNotification().contentView;
- final RemoteViews contentView = notification.getNotification().contentView;
- final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
- final RemoteViews bigContentView = notification.getNotification().bigContentView;
- if (DEBUG) {
- Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
- + " ongoing=" + oldNotification.isOngoing()
- + " expanded=" + oldEntry.expanded
- + " contentView=" + oldContentView
- + " bigContentView=" + oldBigContentView
- + " rowParent=" + oldEntry.row.getParent());
- Log.d(TAG, "new notification: when=" + notification.getNotification().when
- + " ongoing=" + oldNotification.isOngoing()
- + " contentView=" + contentView
- + " bigContentView=" + bigContentView);
- }
- // Can we just reapply the RemoteViews in place? If when didn't change, the order
- // didn't change.
- // 1U is never null
- boolean contentsUnchanged = oldEntry.expanded != null
- && contentView.getPackage() != null
- && oldContentView.getPackage() != null
- && oldContentView.getPackage().equals(contentView.getPackage())
- && oldContentView.getLayoutId() == contentView.getLayoutId();
- // large view may be null
- boolean bigContentsUnchanged =
- (oldEntry.getBigContentView() == null && bigContentView == null)
- || ((oldEntry.getBigContentView() != null && bigContentView != null)
- && bigContentView.getPackage() != null
- && oldBigContentView.getPackage() != null
- && oldBigContentView.getPackage().equals(bigContentView.getPackage())
- && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
- ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
- boolean orderUnchanged = notification.getNotification().when== oldNotification.getNotification().when
- && notification.getScore() == oldNotification.getScore();
- // score now encompasses/supersedes isOngoing()
- boolean updateTicker = notification.getNotification().tickerText != null
- && !TextUtils.equals(notification.getNotification().tickerText,
- oldEntry.notification.getNotification().tickerText);
- boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
- if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
- if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
- oldEntry.notification = notification;
- try {
- updateNotificationViews(oldEntry, notification);
- if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
- && oldNotification == mInterruptingNotificationEntry.notification) {
- if (!shouldInterrupt(notification)) {
- if (DEBUG) Log.d(TAG, "no longer interrupts!");
- mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
- } else {
- if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
- mInterruptingNotificationEntry.notification = notification;
- updateNotificationViews(mInterruptingNotificationEntry, notification);
- }
- }
- // Update the icon.
- final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
- notification.getUser(),
- notification.getNotification().icon, notification.getNotification().iconLevel,
- notification.getNotification().number,
- notification.getNotification().tickerText);
- if (!oldEntry.icon.set(ic)) {
- handleNotificationError(key, notification, "Couldn't update icon: " + ic);
- return;
- }
- updateExpansionStates();
- }
- catch (RuntimeException e) {
- // It failed to add cleanly. Log, and remove the view from the panel.
- Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
- removeNotificationViews(key);
- addNotificationViews(key, notification);
- }
- } else {
- if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
- if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
- if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
- if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
- final boolean wasExpanded = oldEntry.row.isUserExpanded();
- removeNotificationViews(key);
- addNotificationViews(key, notification); // will also replace the heads up
- if (wasExpanded) {
- final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
- newEntry.row.setExpanded(true);
- newEntry.row.setUserExpanded(true);
- }
- }
- // Update the veto button accordingly (and as a result, whether this row is
- // swipe-dismissable)
- updateNotificationVetoButton(oldEntry.row, notification);
- // Is this for you?
- boolean isForCurrentUser = notificationIsForCurrentUser(notification);
- if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
- // Restart the ticker if it's still running
- if (updateTicker && isForCurrentUser) {
- haltTicker();
- tick(key, notification, false);
- }
- // Recalculate the position of the sliding windows and the titles.
- setAreThereNotifications();
- updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
- }
- private void updateNotificationViews(NotificationData.Entry entry,
- StatusBarNotification notification) {
- final RemoteViews contentView = notification.getNotification().contentView;
- final RemoteViews bigContentView = notification.getNotification().bigContentView;
- // Reapply the RemoteViews
- contentView.reapply(mContext, entry.expanded, mOnClickHandler);
- if (bigContentView != null && entry.getBigContentView() != null) {
- bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler);
- }
- // update the contentIntent
- final PendingIntent contentIntent = notification.getNotification().contentIntent;
- if (contentIntent != null) {
- final View.OnClickListener listener = makeClicker(contentIntent,
- notification.getPackageName(), notification.getTag(), notification.getId());
- entry.content.setOnClickListener(listener);
- } else {
- entry.content.setOnClickListener(null);
- }
- }
- protected void notifyHeadsUpScreenOn(boolean screenOn) {
- if (!screenOn && mInterruptingNotificationEntry != null) {
- mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
- }
- }
- protected boolean shouldInterrupt(StatusBarNotification sbn) {
- Notification notification = sbn.getNotification();
- // some predicates to make the boolean logic legible
- boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
- || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
- || notification.sound != null
- || notification.vibrate != null;
- boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
- boolean isFullscreen = notification.fullScreenIntent != null;
- boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
- Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
- final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
- boolean interrupt = (isFullscreen || (isHighPriority && isNoisy))
- && isAllowed
- && mPowerManager.isScreenOn()
- && !keyguard.isShowingAndNotHidden()
- && !keyguard.isInputRestricted();
- try {
- interrupt = interrupt && !mDreamManager.isDreaming();
- } catch (RemoteException e) {
- Log.d(TAG, "failed to query dream manager", e);
- }
- if (DEBUG) Log.d(TAG, "interrupt: " + interrupt);
- return interrupt;
- }
- // Q: What kinds of notifications should show during setup?
- // A: Almost none! Only things coming from the system (package is "android") that also
- // have special "kind" tags marking them as relevant for setup (see below).
- protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
- if ("android".equals(sbn.getPackageName())) {
- if (sbn.getNotification().kind != null) {
- for (String aKind : sbn.getNotification().kind) {
- // IME switcher, created by InputMethodManagerService
- if ("android.system.imeswitcher".equals(aKind)) return true;
- // OTA availability & errors, created by SystemUpdateService
- if ("android.system.update".equals(aKind)) return true;
- }
- }
- }
- return false;
- }
- public boolean inKeyguardRestrictedInputMode() {
- return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
- }
- public void setInteracting(int barWindow, boolean interacting) {
- // hook for subclasses
- }
- public void destroy() {
- if (mSearchPanelView != null) {
- mWindowManager.removeViewImmediate(mSearchPanelView);
- }
- mContext.unregisterReceiver(mBroadcastReceiver);
- }
- }