/* * 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.phone; import android.animation.ObjectAnimator; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.Dialog; import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.StatusBarManager; 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.Resources; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.Handler; import android.os.Message; import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Slog; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.IWindowManager; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.ScrollView; import android.widget.TextView; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.R; import com.android.systemui.recent.RecentTasksLoader; import com.android.systemui.recent.RecentsPanelView; import com.android.systemui.recent.TaskDescription; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.StatusBar; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.policy.DateView; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NotificationRowLayout; public class PhoneStatusBar extends StatusBar { static final String TAG = "PhoneStatusBar"; public static final boolean DEBUG = false; public static final boolean SPEW = false; public static final boolean DUMPTRUCK = true; // extra dumpsys info // additional instrumentation for testing purposes; intended to be left on during development public static final boolean CHATTY = DEBUG; public static final String ACTION_STATUSBAR_START = "com.android.internal.policy.statusbar.START"; static final int EXPANDED_LEAVE_ALONE = -10000; static final int EXPANDED_FULL_OPEN = -10001; private static final int MSG_ANIMATE = 100; private static final int MSG_ANIMATE_REVEAL = 101; private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; private static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; private static final int MSG_SHOW_INTRUDER = 1002; private static final int MSG_HIDE_INTRUDER = 1003; private static final int MSG_OPEN_RECENTS_PANEL = 1020; private static final int MSG_CLOSE_RECENTS_PANEL = 1021; // will likely move to a resource or other tunable param at some point private static final int INTRUDER_ALERT_DECAY_MS = 10000; private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true; // fling gesture tuning parameters, scaled to display density private float mSelfExpandVelocityPx; // classic value: 2000px/s private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up") private float mFlingExpandMinVelocityPx; // classic value: 200px/s private float mFlingCollapseMinVelocityPx; // classic value: 200px/s private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1) private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand) private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s private float mExpandAccelPx; // classic value: 2000px/s/s private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up") PhoneStatusBarPolicy mIconPolicy; // These are no longer handled by the policy, because we need custom strategies for them BatteryController mBatteryController; LocationController mLocationController; NetworkController mNetworkController; int mNaturalBarHeight = -1; int mIconSize = -1; int mIconHPadding = -1; Display mDisplay; IWindowManager mWindowManager; PhoneStatusBarView mStatusBarView; int mPixelFormat; H mHandler = new H(); Object mQueueLock = new Object(); // icons LinearLayout mIcons; IconMerger mNotificationIcons; View mMoreIcon; LinearLayout mStatusIcons; // expanded notifications Dialog mExpandedDialog; ExpandedView mExpandedView; WindowManager.LayoutParams mExpandedParams; ScrollView mScrollView; View mExpandedContents; // top bar TextView mNoNotificationsTitle; View mClearButton; View mSettingsButton; // drag bar CloseDragHandle mCloseView; // all notifications NotificationData mNotificationData = new NotificationData(); NotificationRowLayout mPile; // position int[] mPositionTmp = new int[2]; boolean mExpanded; boolean mExpandedVisible; // the date view DateView mDateView; // for immersive activities private View mIntruderAlertView; // on-screen navigation buttons private NavigationBarView mNavigationBarView = null; // the tracker view TrackingView mTrackingView; WindowManager.LayoutParams mTrackingParams; int mTrackingPosition; // the position of the top of the tracking view. private boolean mPanelSlightlyVisible; // ticker private Ticker mTicker; private View mTickerView; private boolean mTicking; // Recent apps private RecentsPanelView mRecentsPanel; private RecentTasksLoader mRecentTasksLoader; // Tracking finger for opening/closing. int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore boolean mTracking; VelocityTracker mVelocityTracker; static final int ANIM_FRAME_DURATION = (1000/60); boolean mAnimating; long mCurAnimationTime; float mAnimY; float mAnimVel; float mAnimAccel; long mAnimLastTime; boolean mAnimatingReveal = false; int mViewDelta; int[] mAbsPos = new int[2]; Runnable mPostCollapseCleanup = null; // for disabling the status bar int mDisabled = 0; // tracking calls to View.setSystemUiVisibility() int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; DisplayMetrics mDisplayMetrics = new DisplayMetrics(); private class ExpandedDialog extends Dialog { ExpandedDialog(Context context) { super(context, com.android.internal.R.style.Theme_Translucent_NoTitleBar); } @Override public boolean dispatchKeyEvent(KeyEvent event) { boolean down = event.getAction() == KeyEvent.ACTION_DOWN; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_BACK: if (!down) { animateCollapse(); } return true; } return super.dispatchKeyEvent(event); } } @Override public void start() { mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); mWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); super.start(); // calls makeStatusBarView() addNavigationBar(); //addIntruderView(); // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext); } // ================================================================================ // Constructing the view // ================================================================================ protected View makeStatusBarView() { final Context context = mContext; Resources res = context.getResources(); updateDisplaySize(); // populates mDisplayMetrics loadDimens(); mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); ExpandedView expanded = (ExpandedView)View.inflate(context, R.layout.status_bar_expanded, null); if (DEBUG) { expanded.setBackgroundColor(0x6000FF80); } expanded.mService = this; mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null); mIntruderAlertView.setVisibility(View.GONE); mIntruderAlertView.setClickable(true); PhoneStatusBarView sb = (PhoneStatusBarView)View.inflate(context, R.layout.status_bar, null); sb.mService = this; mStatusBarView = sb; try { boolean showNav = mWindowManager.hasNavigationBar(); if (showNav) { mNavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); mNavigationBarView.setDisabledFlags(mDisabled); } } catch (RemoteException ex) { // no window manager? good luck with that } // figure out which pixel-format to use for the status bar. mPixelFormat = PixelFormat.OPAQUE; mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons); mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons); mMoreIcon = sb.findViewById(R.id.moreIcon); mNotificationIcons.setOverflowIndicator(mMoreIcon); mIcons = (LinearLayout)sb.findViewById(R.id.icons); mTickerView = sb.findViewById(R.id.ticker); mExpandedDialog = new ExpandedDialog(context); mExpandedView = expanded; mPile = (NotificationRowLayout)expanded.findViewById(R.id.latestItems); mExpandedContents = mPile; // was: expanded.findViewById(R.id.notificationLinearLayout); mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle); mNoNotificationsTitle.setVisibility(View.GONE); // disabling for now mClearButton = expanded.findViewById(R.id.clear_all_button); mClearButton.setOnClickListener(mClearButtonListener); mClearButton.setAlpha(0f); mClearButton.setEnabled(false); mDateView = (DateView)expanded.findViewById(R.id.date); mSettingsButton = expanded.findViewById(R.id.settings_button); mSettingsButton.setOnClickListener(mSettingsButtonListener); mScrollView = (ScrollView)expanded.findViewById(R.id.scroll); mTicker = new MyTicker(context, sb); TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText); tickerView.mTicker = mTicker; mTrackingView = (TrackingView)View.inflate(context, R.layout.status_bar_tracking, null); mTrackingView.mService = this; mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close); mCloseView.mService = this; mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); // set the inital view visibility setAreThereNotifications(); // Other icons mLocationController = new LocationController(mContext); // will post a notification mBatteryController = new BatteryController(mContext); mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); mNetworkController = new NetworkController(mContext); final SignalClusterView signalCluster = (SignalClusterView)sb.findViewById(R.id.signal_cluster); mNetworkController.addSignalCluster(signalCluster); signalCluster.setNetworkController(mNetworkController); // final ImageView wimaxRSSI = // (ImageView)sb.findViewById(R.id.wimax_signal); // if (wimaxRSSI != null) { // mNetworkController.addWimaxIconView(wimaxRSSI); // } // Recents Panel mRecentTasksLoader = new RecentTasksLoader(context); updateRecentsPanel(); // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); filter.addAction(Intent.ACTION_SCREEN_OFF); context.registerReceiver(mBroadcastReceiver, filter); return sb; } protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) { boolean opaque = false; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( layoutParams.width, layoutParams.height, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT)); if (ActivityManager.isHighEndGfx(mDisplay)) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } lp.gravity = Gravity.BOTTOM | Gravity.LEFT; lp.setTitle("RecentsPanel"); lp.windowAnimations = R.style.Animation_RecentPanel; lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; return lp; } protected void updateRecentsPanel() { // Recents Panel boolean visible = false; ArrayList<TaskDescription> recentTasksList = null; if (mRecentsPanel != null) { visible = mRecentsPanel.isShowing(); WindowManagerImpl.getDefault().removeView(mRecentsPanel); if (visible) { recentTasksList = mRecentsPanel.getRecentTasksList(); } } // Provide RecentsPanelView with a temporary parent to allow layout params to work. LinearLayout tmpRoot = new LinearLayout(mContext); mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_recent_panel, tmpRoot, false); mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader); mRecentTasksLoader.setRecentsPanel(mRecentsPanel); mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, mRecentsPanel)); mRecentsPanel.setVisibility(View.GONE); WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams()); WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); mRecentsPanel.setBar(this); if (visible) { mRecentsPanel.show(true, false, recentTasksList); } } protected int getStatusBarGravity() { return Gravity.TOP | Gravity.FILL_HORIZONTAL; } public int getStatusBarHeight() { final Resources res = mContext.getResources(); return res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); } private View.OnClickListener mRecentsClickListener = new View.OnClickListener() { public void onClick(View v) { toggleRecentApps(); } }; private void prepareNavigationBarView() { mNavigationBarView.reorient(); mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPanel); } // For small-screen devices (read: phones) that lack hardware navigation buttons private void addNavigationBar() { if (mNavigationBarView == null) return; prepareNavigationBarView(); WindowManagerImpl.getDefault().addView( mNavigationBarView, getNavigationBarLayoutParams()); } private void repositionNavigationBar() { if (mNavigationBarView == null) return; prepareNavigationBarView(); WindowManagerImpl.getDefault().updateViewLayout( mNavigationBarView, getNavigationBarLayoutParams()); } private WindowManager.LayoutParams getNavigationBarLayoutParams() { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 0 | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_SLIPPERY, PixelFormat.OPAQUE); // this will allow the navbar to run in an overlay on devices that support this if (ActivityManager.isHighEndGfx(mDisplay)) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } lp.setTitle("NavigationBar"); lp.windowAnimations = 0; return lp; } private void addIntruderView() { final int height = getStatusBarHeight(); WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; lp.y += height * 1.5; // FIXME lp.setTitle("IntruderAlert"); lp.packageName = mContext.getPackageName(); lp.windowAnimations = R.style.Animation_StatusBar_IntruderAlert; WindowManagerImpl.getDefault().addView(mIntruderAlertView, lp); } public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { if (SPEW) Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex + " icon=" + icon); StatusBarIconView view = new StatusBarIconView(mContext, slot, null); view.set(icon); mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize)); } public void updateIcon(String slot, int index, int viewIndex, StatusBarIcon old, StatusBarIcon icon) { if (SPEW) Slog.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex + " old=" + old + " icon=" + icon); StatusBarIconView view = (StatusBarIconView)mStatusIcons.getChildAt(viewIndex); view.set(icon); } public void removeIcon(String slot, int index, int viewIndex) { if (SPEW) Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex); mStatusIcons.removeViewAt(viewIndex); } public void addNotification(IBinder key, StatusBarNotification notification) { StatusBarIconView iconView = addNotificationViews(key, notification); if (iconView == null) return; boolean immersive = false; try { immersive = ActivityManagerNative.getDefault().isTopActivityImmersive(); if (DEBUG) { Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); } } catch (RemoteException ex) { } if (immersive) { if ((notification.notification.flags & Notification.FLAG_HIGH_PRIORITY) != 0) { Slog.d(TAG, "Presenting high-priority notification in immersive activity"); // special new transient ticker mode // 1. Populate mIntruderAlertView ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon); TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText); alertIcon.setImageDrawable(StatusBarIconView.getIcon( alertIcon.getContext(), iconView.getStatusBarIcon())); alertText.setText(notification.notification.tickerText); View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content); button.setOnClickListener( new NotificationClicker(notification.notification.contentIntent, notification.pkg, notification.tag, notification.id)); // 2. Animate mIntruderAlertView in mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER); // 3. Set alarm to age the notification off (TODO) mHandler.removeMessages(MSG_HIDE_INTRUDER); mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS); } } else if (notification.notification.fullScreenIntent != null) { // not immersive & a full-screen alert should be shown Slog.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); try { notification.notification.fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } } else { // usual case: status bar visible & not immersive // show the ticker tick(notification); } // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); } public void updateNotification(IBinder key, StatusBarNotification notification) { if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); if (oldEntry == null) { Slog.w(TAG, "updateNotification for unknown key: " + key); return; } final StatusBarNotification oldNotification = oldEntry.notification; final RemoteViews oldContentView = oldNotification.notification.contentView; final RemoteViews contentView = notification.notification.contentView; if (DEBUG) { Slog.d(TAG, "old notification: when=" + oldNotification.notification.when + " ongoing=" + oldNotification.isOngoing() + " expanded=" + oldEntry.expanded + " contentView=" + oldContentView + " rowParent=" + oldEntry.row.getParent()); Slog.d(TAG, "new notification: when=" + notification.notification.when + " ongoing=" + oldNotification.isOngoing() + " contentView=" + contentView); } // Can we just reapply the RemoteViews in place? If when didn't change, the order // didn't change. boolean contentsUnchanged = oldEntry.expanded != null && contentView != null && oldContentView != null && contentView.getPackage() != null && oldContentView.getPackage() != null && oldContentView.getPackage().equals(contentView.getPackage()) && oldContentView.getLayoutId() == contentView.getLayoutId(); ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); boolean orderUnchanged = notification.notification.when==oldNotification.notification.when && notification.priority == oldNotification.priority; // priority now encompasses isOngoing() boolean updateTicker = notification.notification.tickerText != null && !TextUtils.equals(notification.notification.tickerText, oldEntry.notification.notification.tickerText); boolean isFirstAnyway = rowParent.indexOfChild(oldEntry.row) == 0; if (contentsUnchanged && (orderUnchanged || isFirstAnyway)) { if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); oldEntry.notification = notification; try { // Reapply the RemoteViews contentView.reapply(mContext, oldEntry.content); // update the contentIntent final PendingIntent contentIntent = notification.notification.contentIntent; if (contentIntent != null) { final View.OnClickListener listener = new NotificationClicker(contentIntent, notification.pkg, notification.tag, notification.id); oldEntry.largeIcon.setOnClickListener(listener); oldEntry.content.setOnClickListener(listener); } else { oldEntry.largeIcon.setOnClickListener(null); oldEntry.content.setOnClickListener(null); } // Update the icon. final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon, notification.notification.iconLevel, notification.notification.number, notification.notification.tickerText); if (!oldEntry.icon.set(ic)) { handleNotificationError(key, notification, "Couldn't update icon: " + ic); return; } // Update the large icon if (notification.notification.largeIcon != null) { oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon); } else { oldEntry.largeIcon.getLayoutParams().width = 0; oldEntry.largeIcon.setVisibility(View.INVISIBLE); } } catch (RuntimeException e) { // It failed to add cleanly. Log, and remove the view from the panel. Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); removeNotificationViews(key); addNotificationViews(key, notification); } } else { if (SPEW) Slog.d(TAG, "not reusing notification"); removeNotificationViews(key); addNotificationViews(key, notification); } // Update the veto button accordingly (and as a result, whether this row is // swipe-dismissable) updateNotificationVetoButton(oldEntry.row, notification); // Restart the ticker if it's still running if (updateTicker) { mTicker.halt(); tick(notification); } // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); } public void removeNotification(IBinder key) { if (SPEW) Slog.d(TAG, "removeNotification key=" + key); StatusBarNotification old = removeNotificationViews(key); if (old != null) { // Cancel the ticker if it's still running mTicker.removeEntry(old); // Recalculate the position of the sliding windows and the titles. updateExpandedViewPos(EXPANDED_LEAVE_ALONE); if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0 && !mAnimating) { animateCollapse(); } } setAreThereNotifications(); } @Override protected void onConfigurationChanged(Configuration newConfig) { updateRecentsPanel(); } View[] makeNotificationView(StatusBarNotification notification, ViewGroup parent) { Notification n = notification.notification; RemoteViews remoteViews = n.contentView; if (remoteViews == null) { return null; } // create the row view LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); // wire up the veto button View vetoButton = updateNotificationVetoButton(row, notification); vetoButton.setContentDescription(mContext.getString( R.string.accessibility_remove_notification)); // the large icon ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); if (notification.notification.largeIcon != null) { largeIcon.setImageBitmap(notification.notification.largeIcon); } else { largeIcon.getLayoutParams().width = 0; largeIcon.setVisibility(View.INVISIBLE); } // bind the click event to the content area ViewGroup content = (ViewGroup)row.findViewById(R.id.content); content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); content.setOnFocusChangeListener(mFocusChangeListener); PendingIntent contentIntent = n.contentIntent; if (contentIntent != null) { final View.OnClickListener listener = new NotificationClicker(contentIntent, notification.pkg, notification.tag, notification.id); largeIcon.setOnClickListener(listener); content.setOnClickListener(listener); } else { largeIcon.setOnClickListener(null); content.setOnClickListener(null); } View expanded = null; Exception exception = null; try { expanded = remoteViews.apply(mContext, content); } catch (RuntimeException e) { exception = e; } if (expanded == null) { String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id); Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); return null; } else { content.addView(expanded); row.setDrawingCacheEnabled(true); } applyLegacyRowBackground(notification, content); return new View[] { row, content, expanded }; } StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { if (DEBUG) { Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); } // Construct the icon. final StatusBarIconView iconView = new StatusBarIconView(mContext, notification.pkg + "/0x" + Integer.toHexString(notification.id), notification.notification); iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon, notification.notification.iconLevel, notification.notification.number, notification.notification.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; } // Add the expanded view and icon. int pos = mNotificationData.add(entry); if (DEBUG) { Slog.d(TAG, "addNotificationViews: added at " + pos); } updateNotificationIcons(); return iconView; } private void loadNotificationShade() { int N = mNotificationData.size(); ArrayList<View> toShow = new ArrayList<View>(); for (int i=0; i<N; i++) { View row = mNotificationData.get(N-i-1).row; toShow.add(row); } ArrayList<View> toRemove = new ArrayList<View>(); for (int i=0; i<mPile.getChildCount(); i++) { View child = mPile.getChildAt(i); if (!toShow.contains(child)) { toRemove.add(child); } } for (View remove : toRemove) { mPile.removeView(remove); } for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { mPile.addView(v, 0); // the notification shade has newest at the top } } } private void reloadAllNotificationIcons() { if (mNotificationIcons == null) return; mNotificationIcons.removeAllViews(); updateNotificationIcons(); } private void updateNotificationIcons() { loadNotificationShade(); final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); int N = mNotificationData.size(); if (DEBUG) { Slog.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + mNotificationIcons); } ArrayList<View> toShow = new ArrayList<View>(); for (int i=0; i<N; i++) { toShow.add(mNotificationData.get(N-i-1).icon); } ArrayList<View> toRemove = new ArrayList<View>(); for (int i=0; i<mNotificationIcons.getChildCount(); i++) { View child = mNotificationIcons.getChildAt(i); if (!toShow.contains(child)) { toRemove.add(child); } } for (View remove : toRemove) { mNotificationIcons.removeView(remove); } for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { mNotificationIcons.addView(v, i, params); } } } private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { StatusBarNotification sbn = entry.notification; RemoteViews remoteViews = sbn.notification.contentView; if (remoteViews == null) { return false; } // create the row view LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); View vetoButton = updateNotificationVetoButton(row, sbn); vetoButton.setContentDescription(mContext.getString( R.string.accessibility_remove_notification)); // the large icon ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); if (sbn.notification.largeIcon != null) { largeIcon.setImageBitmap(sbn.notification.largeIcon); largeIcon.setContentDescription(sbn.notification.tickerText); } else { largeIcon.getLayoutParams().width = 0; largeIcon.setVisibility(View.INVISIBLE); } largeIcon.setContentDescription(sbn.notification.tickerText); // bind the click event to the content area ViewGroup content = (ViewGroup)row.findViewById(R.id.content); // XXX: update to allow controls within notification views content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); // content.setOnFocusChangeListener(mFocusChangeListener); PendingIntent contentIntent = sbn.notification.contentIntent; if (contentIntent != null) { final View.OnClickListener listener = new NotificationClicker(contentIntent, sbn.pkg, sbn.tag, sbn.id); largeIcon.setOnClickListener(listener); content.setOnClickListener(listener); } else { largeIcon.setOnClickListener(null); content.setOnClickListener(null); } View expanded = null; Exception exception = null; try { expanded = remoteViews.apply(mContext, content); } catch (RuntimeException e) { exception = e; } if (expanded == null) { final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); return false; } else { content.addView(expanded); row.setDrawingCacheEnabled(true); } applyLegacyRowBackground(sbn, content); entry.row = row; entry.content = content; entry.expanded = expanded; entry.largeIcon = largeIcon; return true; } void applyLegacyRowBackground(StatusBarNotification sbn, View content) { if (sbn.notification.contentView.getLayoutId() != com.android.internal.R.layout.status_bar_latest_event_content) { int version = 0; try { ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); version = info.targetSdkVersion; } catch (NameNotFoundException ex) { Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); } if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { content.setBackgroundResource(R.drawable.notification_row_legacy_bg); } else { content.setBackgroundResource(R.drawable.notification_row_bg); } } } StatusBarNotification removeNotificationViews(IBinder key) { NotificationData.Entry entry = mNotificationData.remove(key); if (entry == null) { Slog.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); updateNotificationIcons(); return entry.notification; } private void setAreThereNotifications() { final boolean any = mNotificationData.size() > 0; final boolean clearable = any && mNotificationData.hasClearableItems(); if (DEBUG) { Slog.d(TAG, "setAreThereNotifications: N=" + mNotificationData.size() + " any=" + any + " clearable=" + clearable); } if (mClearButton.isShown()) { if (clearable != (mClearButton.getAlpha() == 1.0f)) { ObjectAnimator.ofFloat(mClearButton, "alpha", clearable ? 1.0f : 0.0f) .setDuration(250) .start(); } } else { mClearButton.setAlpha(clearable ? 1.0f : 0.0f); } mClearButton.setEnabled(clearable); /* if (mNoNotificationsTitle.isShown()) { if (any != (mNoNotificationsTitle.getAlpha() == 0.0f)) { ObjectAnimator a = ObjectAnimator.ofFloat(mNoNotificationsTitle, "alpha", (any ? 0.0f : 0.75f)); a.setDuration(any ? 0 : 500); a.setStartDelay(any ? 250 : 1000); a.start(); } } else { mNoNotificationsTitle.setAlpha(any ? 0.0f : 0.75f); } */ } public void showClock(boolean show) { View clock = mStatusBarView.findViewById(R.id.clock); if (clock != null) { clock.setVisibility(show ? View.VISIBLE : View.GONE); } } /** * State is one or more of the DISABLE constants from StatusBarManager. */ public void disable(int state) { final int old = mDisabled; final int diff = state ^ old; mDisabled = state; if (DEBUG) { Slog.d(TAG, String.format("disable: 0x%08x -> 0x%08x (diff: 0x%08x)", old, state, diff)); } StringBuilder flagdbg = new StringBuilder(); flagdbg.append("disable: < "); flagdbg.append(((state & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand"); flagdbg.append(((diff & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons"); flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts"); flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "TICKER" : "ticker"); flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info"); flagdbg.append(((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back"); flagdbg.append(((diff & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home"); flagdbg.append(((diff & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent"); flagdbg.append(((diff & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " "); flagdbg.append(((state & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock"); flagdbg.append(((diff & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " "); flagdbg.append(">"); Slog.d(TAG, flagdbg.toString()); if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; showClock(show); } if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { animateCollapse(); } } if ((diff & (StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_RECENT | StatusBarManager.DISABLE_BACK)) != 0) { // the nav bar will take care of these if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state); if ((state & StatusBarManager.DISABLE_RECENT) != 0) { // close recents if it's visible mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); } } if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if (mTicking) { mTicker.halt(); } else { setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out); } } else { if (!mExpandedVisible) { setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); } } } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { mTicker.halt(); } } } /** * All changes to the status bar and notifications funnel through here and are batched. */ private class H extends Handler { public void handleMessage(Message m) { switch (m.what) { case MSG_ANIMATE: doAnimation(); break; case MSG_ANIMATE_REVEAL: doRevealAnimation(); break; case MSG_OPEN_NOTIFICATION_PANEL: animateExpand(); break; case MSG_CLOSE_NOTIFICATION_PANEL: animateCollapse(); break; case MSG_SHOW_INTRUDER: setIntruderAlertVisibility(true); break; case MSG_HIDE_INTRUDER: setIntruderAlertVisibility(false); break; case MSG_OPEN_RECENTS_PANEL: if (DEBUG) Slog.d(TAG, "opening recents panel"); if (mRecentsPanel != null) { mRecentsPanel.show(true, true); } break; case MSG_CLOSE_RECENTS_PANEL: if (DEBUG) Slog.d(TAG, "closing recents panel"); if (mRecentsPanel != null && mRecentsPanel.isShowing()) { mRecentsPanel.show(false, true); } break; } } } View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { // Because 'v' is a ViewGroup, all its children will be (un)selected // too, which allows marqueeing to work. v.setSelected(hasFocus); } }; private void makeExpandedVisible() { if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); if (mExpandedVisible) { return; } mExpandedVisible = true; visibilityChanged(true); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; if (DEBUG) { Slog.d(TAG, "makeExpandedVisible: expanded params = " + mExpandedParams); } mExpandedDialog.getWindow().setAttributes(mExpandedParams); mExpandedView.requestFocus(View.FOCUS_FORWARD); mTrackingView.setVisibility(View.VISIBLE); } public void animateExpand() { if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded); if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { return ; } if (mExpanded) { return; } prepareTracking(0, true); performFling(0, mSelfExpandVelocityPx, true); } public void animateCollapse() { animateCollapse(false); } public void animateCollapse(boolean excludeRecents) { animateCollapse(excludeRecents, 1.0f); } public void animateCollapse(boolean excludeRecents, float velocityMultiplier) { if (SPEW) { Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded + " mExpandedVisible=" + mExpandedVisible + " mExpanded=" + mExpanded + " mAnimating=" + mAnimating + " mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); } if (!excludeRecents) { mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); } if (!mExpandedVisible) { return; } int y; if (mAnimating) { y = (int)mAnimY; } else { y = mDisplayMetrics.heightPixels-1; } // Let the fling think that we're open so it goes in the right direction // and doesn't try to re-open the windowshade. mExpanded = true; prepareTracking(y, false); performFling(y, -mSelfCollapseVelocityPx*velocityMultiplier, true); } void performExpand() { if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded); if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { return ; } if (mExpanded) { return; } mExpanded = true; makeExpandedVisible(); updateExpandedViewPos(EXPANDED_FULL_OPEN); if (false) postStartTracing(); } void performCollapse() { if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded + " mExpandedVisible=" + mExpandedVisible); if (!mExpandedVisible) { return; } mExpandedVisible = false; visibilityChanged(false); mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; mExpandedDialog.getWindow().setAttributes(mExpandedParams); mTrackingView.setVisibility(View.GONE); if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); } if (!mExpanded) { return; } mExpanded = false; if (mPostCollapseCleanup != null) { mPostCollapseCleanup.run(); mPostCollapseCleanup = null; } } void doAnimation() { if (mAnimating) { if (SPEW) Slog.d(TAG, "doAnimation"); if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY); incrementAnim(); if (SPEW) Slog.d(TAG, "doAnimation after mAnimY=" + mAnimY); if (mAnimY >= mDisplayMetrics.heightPixels-1) { if (SPEW) Slog.d(TAG, "Animation completed to expanded state."); mAnimating = false; updateExpandedViewPos(EXPANDED_FULL_OPEN); performExpand(); } else if (mAnimY < mStatusBarView.getHeight()) { if (SPEW) Slog.d(TAG, "Animation completed to collapsed state."); mAnimating = false; updateExpandedViewPos(0); performCollapse(); } else { updateExpandedViewPos((int)mAnimY); mCurAnimationTime += ANIM_FRAME_DURATION; mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); } } } void stopTracking() { mTracking = false; mVelocityTracker.recycle(); mVelocityTracker = null; } void incrementAnim() { long now = SystemClock.uptimeMillis(); float t = ((float)(now - mAnimLastTime)) / 1000; // ms -> s final float y = mAnimY; final float v = mAnimVel; // px/s final float a = mAnimAccel; // px/s/s mAnimY = y + (v*t) + (0.5f*a*t*t); // px mAnimVel = v + (a*t); // px/s mAnimLastTime = now; // ms //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY // + " mAnimAccel=" + mAnimAccel); } void doRevealAnimation() { final int h = mCloseView.getHeight() + mStatusBarView.getHeight(); if (mAnimatingReveal && mAnimating && mAnimY < h) { incrementAnim(); if (mAnimY >= h) { mAnimY = h; updateExpandedViewPos((int)mAnimY); } else { updateExpandedViewPos((int)mAnimY); mCurAnimationTime += ANIM_FRAME_DURATION; mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), mCurAnimationTime); } } } void prepareTracking(int y, boolean opening) { if (CHATTY) { Slog.d(TAG, "panel: beginning to track the user's touch, y=" + y + " opening=" + opening); } // there are some race conditions that cause this to be inaccurate; let's recalculate it any // time we're about to drag the panel updateExpandedSize(); mTracking = true; mVelocityTracker = VelocityTracker.obtain(); if (opening) { mAnimAccel = mExpandAccelPx; mAnimVel = mFlingExpandMinVelocityPx; mAnimY = mStatusBarView.getHeight(); updateExpandedViewPos((int)mAnimY); mAnimating = true; mAnimatingReveal = true; mHandler.removeMessages(MSG_ANIMATE); mHandler.removeMessages(MSG_ANIMATE_REVEAL); long now = SystemClock.uptimeMillis(); mAnimLastTime = now; mCurAnimationTime = now + ANIM_FRAME_DURATION; mAnimating = true; mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), mCurAnimationTime); makeExpandedVisible(); } else { // it's open, close it? if (mAnimating) { mAnimating = false; mHandler.removeMessages(MSG_ANIMATE); } updateExpandedViewPos(y + mViewDelta); } } void performFling(int y, float vel, boolean always) { if (CHATTY) { Slog.d(TAG, "panel: will fling, y=" + y + " vel=" + vel); } mAnimatingReveal = false; mAnimY = y; mAnimVel = vel; //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); if (mExpanded) { if (!always && ( vel > mFlingCollapseMinVelocityPx || (y > (mDisplayMetrics.heightPixels*(1f-mCollapseMinDisplayFraction)) && vel > -mFlingExpandMinVelocityPx))) { // We are expanded, but they didn't move sufficiently to cause // us to retract. Animate back to the expanded position. mAnimAccel = mExpandAccelPx; if (vel < 0) { mAnimVel = 0; } } else { // We are expanded and are now going to animate away. mAnimAccel = -mCollapseAccelPx; if (vel > 0) { mAnimVel = 0; } } } else { if (always || ( vel > mFlingExpandMinVelocityPx || (y > (mDisplayMetrics.heightPixels*(1f-mExpandMinDisplayFraction)) && vel > -mFlingCollapseMinVelocityPx))) { // We are collapsed, and they moved enough to allow us to // expand. Animate in the notifications. mAnimAccel = mExpandAccelPx; if (vel < 0) { mAnimVel = 0; } } else { // We are collapsed, but they didn't move sufficiently to cause // us to retract. Animate back to the collapsed position. mAnimAccel = -mCollapseAccelPx; if (vel > 0) { mAnimVel = 0; } } } //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel // + " mAnimAccel=" + mAnimAccel); long now = SystemClock.uptimeMillis(); mAnimLastTime = now; mCurAnimationTime = now + ANIM_FRAME_DURATION; mAnimating = true; mHandler.removeMessages(MSG_ANIMATE); mHandler.removeMessages(MSG_ANIMATE_REVEAL); mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); stopTracking(); } boolean interceptTouchEvent(MotionEvent event) { if (SPEW) { Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled=" + mDisabled); } else if (CHATTY) { if (event.getAction() != MotionEvent.ACTION_MOVE) { Slog.d(TAG, String.format( "panel: %s at (%f, %f) mDisabled=0x%08x", MotionEvent.actionToString(event.getAction()), event.getRawX(), event.getRawY(), mDisabled)); } } if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { return false; } final int action = event.getAction(); final int statusBarSize = mStatusBarView.getHeight(); final int hitSize = statusBarSize*2; final int y = (int)event.getRawY(); if (action == MotionEvent.ACTION_DOWN) { if (!mExpanded) { mViewDelta = statusBarSize - y; } else { mTrackingView.getLocationOnScreen(mAbsPos); mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y; } if ((!mExpanded && y < hitSize) || (mExpanded && y > (mDisplayMetrics.heightPixels-hitSize))) { // We drop events at the edge of the screen to make the windowshade come // down by accident less, especially when pushing open a device with a keyboard // that rotates (like g1 and droid) int x = (int)event.getRawX(); final int edgeBorder = mEdgeBorder; if (x >= edgeBorder && x < mDisplayMetrics.widthPixels - edgeBorder) { prepareTracking(y, !mExpanded);// opening if we're not already fully visible trackMovement(event); } } } else if (mTracking) { trackMovement(event); final int minY = statusBarSize + mCloseView.getHeight(); if (action == MotionEvent.ACTION_MOVE) { if (mAnimatingReveal && y < minY) { // nothing } else { mAnimatingReveal = false; updateExpandedViewPos(y + mViewDelta); } } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { mVelocityTracker.computeCurrentVelocity(1000); float yVel = mVelocityTracker.getYVelocity(); boolean negative = yVel < 0; float xVel = mVelocityTracker.getXVelocity(); if (xVel < 0) { xVel = -xVel; } if (xVel > mFlingGestureMaxXVelocityPx) { xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis } float vel = (float)Math.hypot(yVel, xVel); if (negative) { vel = -vel; } if (CHATTY) { Slog.d(TAG, String.format("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity(), xVel, yVel, vel)); } performFling(y + mViewDelta, vel, false); } } return false; } private void trackMovement(MotionEvent event) { // Add movement to velocity tracker using raw screen X and Y coordinates instead // of window coordinates because the window frame may be moving at the same time. float deltaX = event.getRawX() - event.getX(); float deltaY = event.getRawY() - event.getY(); event.offsetLocation(deltaX, deltaY); mVelocityTracker.addMovement(event); event.offsetLocation(-deltaX, -deltaY); } @Override // CommandQueue public void setSystemUiVisibility(int vis) { final int old = mSystemUiVisibility; final int diff = vis ^ old; if (diff != 0) { mSystemUiVisibility = vis; if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) { final boolean lightsOut = (0 != (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE)); if (lightsOut) { animateCollapse(); } if (mNavigationBarView != null) { mNavigationBarView.setLowProfile(lightsOut); } } notifyUiVisibilityChanged(); } } public void setLightsOn(boolean on) { Log.v(TAG, "setLightsOn(" + on + ")"); if (on) { setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE); } else { setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE); } } private void notifyUiVisibilityChanged() { try { mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility); } catch (RemoteException ex) { } } public void topAppWindowChanged(boolean showMenu) { if (DEBUG) { Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); } if (mNavigationBarView != null) { mNavigationBarView.setMenuVisibility(showMenu); } // See above re: lights-out policy for legacy apps. if (showMenu) setLightsOn(true); } // Not supported public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { } @Override public void setHardKeyboardStatus(boolean available, boolean enabled) { } public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { return new NotificationClicker(intent, pkg, tag, id); } private class NotificationClicker implements View.OnClickListener { private PendingIntent mIntent; private String mPkg; private String mTag; private int mId; 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. Slog.w(TAG, "Sending contentIntent failed: " + e); } KeyguardManager kgm = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); if (kgm != null) kgm.exitKeyguardSecurely(null); } try { mBarService.onNotificationClick(mPkg, mTag, mId); } catch (RemoteException ex) { // system process is dead if we're here. } // close the shade if it was open animateCollapse(); // If this click was on the intruder alert, hide that instead mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); } } private void tick(StatusBarNotification n) { // Show the ticker if one is requested. Also don't do this // until status bar window is attached to the window manager, // because... well, what's the point otherwise? And trying to // run a ticker without being attached will crash! if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) { if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { mTicker.addEntry(n); } } } /** * 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.pkg, n.tag, n.id, n.uid, n.initialPid, message); } catch (RemoteException ex) { // The end is nigh. } } private class MyTicker extends Ticker { MyTicker(Context context, View sb) { super(context, sb); } @Override public void tickerStarting() { mTicking = true; mIcons.setVisibility(View.GONE); mTickerView.setVisibility(View.VISIBLE); mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null)); mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null)); } @Override public void tickerDone() { mIcons.setVisibility(View.VISIBLE); mTickerView.setVisibility(View.GONE); mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null)); mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out, mTickingDoneListener)); } public void tickerHalting() { mIcons.setVisibility(View.VISIBLE); mTickerView.setVisibility(View.GONE); mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out, mTickingDoneListener)); } } Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {; public void onAnimationEnd(Animation animation) { mTicking = false; } public void onAnimationRepeat(Animation animation) { } public void onAnimationStart(Animation animation) { } }; private Animation loadAnim(int id, Animation.AnimationListener listener) { Animation anim = AnimationUtils.loadAnimation(mContext, id); if (listener != null) { anim.setAnimationListener(listener); } return anim; } public static String viewInfo(View v) { return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() + ") " + v.getWidth() + "x" + v.getHeight() + "]"; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mQueueLock) { pw.println("Current Status Bar state:"); pw.println(" mExpanded=" + mExpanded + ", mExpandedVisible=" + mExpandedVisible); pw.println(" mTicking=" + mTicking); pw.println(" mTracking=" + mTracking); pw.println(" mAnimating=" + mAnimating + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel + ", mAnimAccel=" + mAnimAccel); pw.println(" mCurAnimationTime=" + mCurAnimationTime + " mAnimLastTime=" + mAnimLastTime); pw.println(" mAnimatingReveal=" + mAnimatingReveal + " mViewDelta=" + mViewDelta); pw.println(" mDisplayMetrics=" + mDisplayMetrics); pw.println(" mExpandedParams: " + mExpandedParams); pw.println(" mExpandedView: " + viewInfo(mExpandedView)); pw.println(" mExpandedDialog: " + mExpandedDialog); pw.println(" mTrackingParams: " + mTrackingParams); pw.println(" mTrackingView: " + viewInfo(mTrackingView)); pw.println(" mPile: " + viewInfo(mPile)); pw.println(" mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle)); pw.println(" mCloseView: " + viewInfo(mCloseView)); pw.println(" mTickerView: " + viewInfo(mTickerView)); pw.println(" mScrollView: " + viewInfo(mScrollView) + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY()); } pw.print(" mNavigationBarView="); if (mNavigationBarView == null) { pw.println("null"); } else { mNavigationBarView.dump(fd, pw, args); } if (DUMPTRUCK) { synchronized (mNotificationData) { int N = mNotificationData.size(); pw.println(" notification icons: " + N); for (int i=0; i<N; i++) { NotificationData.Entry e = mNotificationData.get(i); pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon); StatusBarNotification n = e.notification; pw.println(" pkg=" + n.pkg + " id=" + n.id + " priority=" + n.priority); pw.println(" notification=" + n.notification); pw.println(" tickerText=\"" + n.notification.tickerText + "\""); } } int N = mStatusIcons.getChildCount(); pw.println(" system icons: " + N); for (int i=0; i<N; i++) { StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i); pw.println(" [" + i + "] icon=" + ic); } if (false) { pw.println("see the logcat for a dump of the views we have created."); // must happen on ui thread mHandler.post(new Runnable() { public void run() { mStatusBarView.getLocationOnScreen(mAbsPos); Slog.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] + ") " + mStatusBarView.getWidth() + "x" + mStatusBarView.getHeight()); mStatusBarView.debug(); mExpandedView.getLocationOnScreen(mAbsPos); Slog.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] + ") " + mExpandedView.getWidth() + "x" + mExpandedView.getHeight()); mExpandedView.debug(); mTrackingView.getLocationOnScreen(mAbsPos); Slog.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] + ") " + mTrackingView.getWidth() + "x" + mTrackingView.getHeight()); mTrackingView.debug(); } }); } } mNetworkController.dump(fd, pw, args); } void onBarViewAttached() { // The status bar has just been attached to the view hierarchy; it's possible that the // screen has rotated in-between when we set up the window and now, so let's double-check // the display metrics just in case. updateDisplaySize(); WindowManager.LayoutParams lp; int pixelFormat; Drawable bg; /// ---------- Tracking View -------------- bg = mTrackingView.getBackground(); if (bg != null) { pixelFormat = bg.getOpacity(); } lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL, 0 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, PixelFormat.TRANSLUCENT); if (ActivityManager.isHighEndGfx(mDisplay)) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } // lp.token = mStatusBarView.getWindowToken(); lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; lp.setTitle("TrackingView"); lp.y = mTrackingPosition; mTrackingParams = lp; WindowManagerImpl.getDefault().addView(mTrackingView, lp); } void onTrackingViewAttached() { WindowManager.LayoutParams lp; int pixelFormat; /// ---------- Expanded View -------------- pixelFormat = PixelFormat.TRANSLUCENT; lp = mExpandedDialog.getWindow().getAttributes(); lp.x = 0; mTrackingPosition = lp.y = mDisplayMetrics.heightPixels; // sufficiently large negative lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; lp.flags = 0 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_DITHER | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; if (ActivityManager.isHighEndGfx(mDisplay)) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } lp.format = pixelFormat; lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; lp.setTitle("StatusBarExpanded"); mExpandedParams = lp; updateExpandedSize(); mExpandedDialog.getWindow().setFormat(pixelFormat); mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); mExpandedDialog.setContentView(mExpandedView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mExpandedDialog.getWindow().setBackgroundDrawable(null); mExpandedDialog.show(); } void setNotificationIconVisibility(boolean visible, int anim) { int old = mNotificationIcons.getVisibility(); int v = visible ? View.VISIBLE : View.INVISIBLE; if (old != v) { mNotificationIcons.setVisibility(v); mNotificationIcons.startAnimation(loadAnim(anim, null)); } } void updateExpandedInvisiblePosition() { if (mTrackingView != null) { mTrackingPosition = -mDisplayMetrics.heightPixels; if (mTrackingParams != null) { mTrackingParams.y = mTrackingPosition; WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); } } if (mExpandedParams != null) { mExpandedParams.y = -mDisplayMetrics.heightPixels; mExpandedDialog.getWindow().setAttributes(mExpandedParams); } } void updateExpandedViewPos(int expandedPosition) { if (SPEW) { Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition + " mTrackingParams.y=" + ((mTrackingParams == null) ? "?" : mTrackingParams.y) + " mTrackingPosition=" + mTrackingPosition); } int h = mStatusBarView.getHeight(); int disph = mDisplayMetrics.heightPixels; // If the expanded view is not visible, make sure they're still off screen. // Maybe the view was resized. if (!mExpandedVisible) { updateExpandedInvisiblePosition(); return; } // tracking view... int pos; if (expandedPosition == EXPANDED_FULL_OPEN) { pos = h; } else if (expandedPosition == EXPANDED_LEAVE_ALONE) { pos = mTrackingPosition; } else { if (expandedPosition <= disph) { pos = expandedPosition; } else { pos = disph; } pos -= disph-h; } mTrackingPosition = mTrackingParams.y = pos; mTrackingParams.height = disph-h; WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); if (mExpandedParams != null) { if (mCloseView.getWindowVisibility() == View.VISIBLE) { mCloseView.getLocationInWindow(mPositionTmp); final int closePos = mPositionTmp[1]; mExpandedContents.getLocationInWindow(mPositionTmp); final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight(); mExpandedParams.y = pos + mTrackingView.getHeight() - (mTrackingParams.height-closePos) - contentsBottom; if (SPEW) { Slog.d(PhoneStatusBar.TAG, "pos=" + pos + " trackingHeight=" + mTrackingView.getHeight() + " (trackingParams.height - closePos)=" + (mTrackingParams.height - closePos) + " contentsBottom=" + contentsBottom); } } else { // If the tracking view is not yet visible, then we can't have // a good value of the close view location. We need to wait for // it to be visible to do a layout. mExpandedParams.y = -mDisplayMetrics.heightPixels; } int max = h; if (mExpandedParams.y > max) { mExpandedParams.y = max; } int min = mTrackingPosition; if (mExpandedParams.y < min) { mExpandedParams.y = min; } boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h; if (!visible) { // if the contents aren't visible, move the expanded view way off screen // because the window itself extends below the content view. mExpandedParams.y = -disph; } mExpandedDialog.getWindow().setAttributes(mExpandedParams); // As long as this isn't just a repositioning that's not supposed to affect // the user's perception of what's showing, call to say that the visibility // has changed. (Otherwise, someone else will call to do that). if (expandedPosition != EXPANDED_LEAVE_ALONE) { if (SPEW) Slog.d(TAG, "updateExpandedViewPos visibilityChanged(" + visible + ")"); visibilityChanged(visible); } } if (SPEW) { Slog.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition + " mTrackingParams.y=" + mTrackingParams.y + " mTrackingPosition=" + mTrackingPosition + " mExpandedParams.y=" + mExpandedParams.y + " mExpandedParams.height=" + mExpandedParams.height); } } int getExpandedHeight(int disph) { if (DEBUG) { Slog.d(TAG, "getExpandedHeight(" + disph + "): sbView=" + mStatusBarView.getHeight() + " closeView=" + mCloseView.getHeight()); } return disph - mStatusBarView.getHeight() - mCloseView.getHeight(); } void updateDisplaySize() { mDisplay.getMetrics(mDisplayMetrics); if (DEBUG) { Slog.d(TAG, "updateDisplaySize: " + mDisplayMetrics); } updateExpandedSize(); } void updateExpandedSize() { if (DEBUG) { Slog.d(TAG, "updateExpandedSize()"); } if (mExpandedDialog != null && mExpandedParams != null && mDisplayMetrics != null) { mExpandedParams.width = mDisplayMetrics.widthPixels; mExpandedParams.height = getExpandedHeight(mDisplayMetrics.heightPixels); if (!mExpandedVisible) { updateExpandedInvisiblePosition(); } else { mExpandedDialog.getWindow().setAttributes(mExpandedParams); } if (DEBUG) { Slog.d(TAG, "updateExpandedSize: height=" + mExpandedParams.height + " " + (mExpandedVisible ? "VISIBLE":"INVISIBLE")); } } } public void toggleRecentApps() { int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; mHandler.removeMessages(msg); mHandler.sendEmptyMessage(msg); } /** * 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) */ void visibilityChanged(boolean visible) { if (mPanelSlightlyVisible != visible) { mPanelSlightlyVisible = visible; try { mBarService.onPanelRevealed(); } catch (RemoteException ex) { // Won't fail unless the world has ended. } } } void performDisableActions(int net) { int old = mDisabled; int diff = net ^ old; mDisabled = net; // act accordingly if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { if ((net & StatusBarManager.DISABLE_EXPAND) != 0) { Slog.d(TAG, "DISABLE_EXPAND: yes"); animateCollapse(); } } if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); if (mTicking) { mNotificationIcons.setVisibility(View.INVISIBLE); mTicker.halt(); } else { setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out); } } else { Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); if (!mExpandedVisible) { setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); } } } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { mTicker.halt(); } } } private View.OnClickListener mClearButtonListener = new View.OnClickListener() { final int mini(int a, int b) { return (b>a?a:b); } public void onClick(View v) { synchronized (mNotificationData) { // animate-swipe all dismissable notifications, then animate the shade closed int numChildren = mPile.getChildCount(); int scrollTop = mScrollView.getScrollY(); int scrollBottom = scrollTop + mScrollView.getHeight(); final ArrayList<View> snapshot = new ArrayList<View>(numChildren); for (int i=0; i<numChildren; i++) { final View child = mPile.getChildAt(i); if (mPile.canChildBeDismissed(child) && child.getBottom() > scrollTop && child.getTop() < scrollBottom) { snapshot.add(child); } } final int N = snapshot.size(); new Thread(new Runnable() { @Override public void run() { // Decrease the delay for every row we animate to give the sense of // accelerating the swipes final int ROW_DELAY_DECREMENT = 10; int currentDelay = 140; int totalDelay = 0; // Set the shade-animating state to avoid doing other work during // all of these animations. In particular, avoid layout and // redrawing when collapsing the shade. mPile.setViewRemoval(false); mPostCollapseCleanup = new Runnable() { public void run() { try { mPile.setViewRemoval(true); mBarService.onClearAllNotifications(); } catch (Exception ex) { } } }; View sampleView = snapshot.get(0); int width = sampleView.getWidth(); final int velocity = (int)(width * 8); // 1000/8 = 125 ms duration for (View v : snapshot) { final View _v = v; mHandler.postDelayed(new Runnable() { @Override public void run() { mPile.dismissRowAnimated(_v, velocity); } }, totalDelay); currentDelay = Math.max(50, currentDelay - ROW_DELAY_DECREMENT); totalDelay += currentDelay; } // Delay the collapse animation until after all swipe animations have // finished. Provide some buffer because there may be some extra delay // before actually starting each swipe animation. Ideally, we'd // synchronize the end of those animations with the start of the collaps // exactly. mHandler.postDelayed(new Runnable() { public void run() { animateCollapse(false); } }, totalDelay + 225); } }).start(); } } }; private View.OnClickListener mSettingsButtonListener = new View.OnClickListener() { public void onClick(View v) { try { // Dismiss the lock screen when Settings starts. ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); } catch (RemoteException e) { } v.getContext().startActivity(new Intent(Settings.ACTION_SETTINGS) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); animateCollapse(); } }; private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) || Intent.ACTION_SCREEN_OFF.equals(action)) { boolean excludeRecents = false; if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { String reason = intent.getStringExtra("reason"); if (reason != null) { excludeRecents = reason.equals("recentapps"); } } animateCollapse(excludeRecents); } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { repositionNavigationBar(); updateResources(); } } }; private void setIntruderAlertVisibility(boolean vis) { mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE); } /** * Reload some of our resources when the configuration changes. * * We don't reload everything when the configuration changes -- we probably * should, but getting that smooth is tough. Someday we'll fix that. In the * meantime, just update the things that we know change. */ void updateResources() { final Context context = mContext; final Resources res = context.getResources(); if (mClearButton instanceof TextView) { ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button)); } mNoNotificationsTitle.setText(context.getText(R.string.status_bar_no_notifications_title)); loadDimens(); } protected void loadDimens() { final Resources res = mContext.getResources(); mNaturalBarHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_height); int newIconSize = res.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); int newIconHPadding = res.getDimensionPixelSize( R.dimen.status_bar_icon_padding); if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { // Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); mIconHPadding = newIconHPadding; mIconSize = newIconSize; //reloadAllNotificationIcons(); // reload the tray } mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity); mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity); mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity); mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity); mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1); mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1); mExpandAccelPx = res.getDimension(R.dimen.expand_accel); mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel); mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity); if (false) Slog.v(TAG, "updateResources"); } // // tracing // void postStartTracing() { mHandler.postDelayed(mStartTracing, 3000); } void vibrate() { android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService( Context.VIBRATOR_SERVICE); vib.vibrate(250); } Runnable mStartTracing = new Runnable() { public void run() { vibrate(); SystemClock.sleep(250); Slog.d(TAG, "startTracing"); android.os.Debug.startMethodTracing("/data/statusbar-traces/trace"); mHandler.postDelayed(mStopTracing, 10000); } }; Runnable mStopTracing = new Runnable() { public void run() { android.os.Debug.stopMethodTracing(); Slog.d(TAG, "stopTracing"); vibrate(); } }; public class TouchOutsideListener implements View.OnTouchListener { private int mMsg; private RecentsPanelView mPanel; public TouchOutsideListener(int msg, RecentsPanelView 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; } } }