| /* |
| * 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.res.Resources; |
| import android.content.res.Configuration; |
| import android.graphics.PixelFormat; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.LayerDrawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| 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.Surface; |
| 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.RecentsPanelView; |
| 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; |
| |
| 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 || true; |
| |
| 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 = 1000; |
| private static final int MSG_ANIMATE_REVEAL = 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; |
| |
| // 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; |
| 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(); |
| ViewGroup 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; |
| |
| // 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]; |
| |
| // 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(); |
| |
| 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(); |
| |
| mDisplay.getMetrics(mDisplayMetrics); |
| if (DEBUG) { |
| Slog.d(TAG, "makeStatusBarView: mDisplayMetrics=" + mDisplayMetrics); |
| mDisplayMetrics = res.getDisplayMetrics(); |
| Slog.d(TAG, "makeStatusBarView: mDisplayMetrics2=" + 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 = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); |
| if (showNav) { |
| mNavigationBarView = |
| (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); |
| |
| mNavigationBarView.setDisabledFlags(mDisabled); |
| |
| sb.setOnSystemUiVisibilityChangeListener( |
| new View.OnSystemUiVisibilityChangeListener() { |
| @Override |
| public void onSystemUiVisibilityChange(int visibility) { |
| if (DEBUG) { |
| Slog.d(TAG, "systemUi: " + visibility); |
| } |
| boolean hide = (0 != (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)); |
| mNavigationBarView.setHidden(hide); |
| } |
| }); |
| } |
| } catch (Resources.NotFoundException ex) { |
| // no nav bar for you |
| } |
| |
| // 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); |
| mIcons = (LinearLayout)sb.findViewById(R.id.icons); |
| mTickerView = sb.findViewById(R.id.ticker); |
| |
| mExpandedDialog = new ExpandedDialog(context); |
| mExpandedView = expanded; |
| mPile = (ViewGroup)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); |
| 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); |
| |
| // Recents Panel |
| 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; |
| if (mRecentsPanel != null) { |
| visible = mRecentsPanel.isShowing(); |
| WindowManagerImpl.getDefault().removeView(mRecentsPanel); |
| } |
| |
| // 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.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); |
| } |
| |
| } |
| |
| 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(); |
| } |
| }; |
| |
| // For small-screen devices (read: phones) that lack hardware navigation buttons |
| private void addNavigationBar() { |
| if (mNavigationBarView == null) return; |
| |
| mNavigationBarView.reorient(); |
| |
| mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); |
| |
| WindowManagerImpl.getDefault().addView( |
| mNavigationBarView, getNavigationBarLayoutParams()); |
| } |
| |
| private void repositionNavigationBar() { |
| if (mNavigationBarView == null) return; |
| |
| mNavigationBarView.reorient(); |
| |
| mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); |
| |
| 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_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 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); |
| } |
| |
| // Restart the ticker if it's still running |
| if (notification.notification.tickerText != null |
| && !TextUtils.equals(notification.notification.tickerText, |
| oldEntry.notification.notification.tickerText)) { |
| 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); |
| } |
| |
| 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 = row.findViewById(R.id.veto); |
| if (notification.isClearable()) { |
| final String _pkg = notification.pkg; |
| final String _tag = notification.tag; |
| final int _id = notification.id; |
| vetoButton.setOnClickListener(new View.OnClickListener() { |
| public void onClick(View v) { |
| try { |
| mBarService.onNotificationClear(_pkg, _tag, _id); |
| } catch (RemoteException ex) { |
| // system process is dead if we're here. |
| } |
| } |
| }); |
| } else { |
| vetoButton.setVisibility(View.GONE); |
| } |
| 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); |
| } |
| |
| 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 = row.findViewById(R.id.veto); |
| if (entry.notification.isClearable()) { |
| final String _pkg = sbn.pkg; |
| final String _tag = sbn.tag; |
| final int _id = sbn.id; |
| vetoButton.setOnClickListener(new View.OnClickListener() { |
| public void onClick(View v) { |
| try { |
| mBarService.onNotificationClear(_pkg, _tag, _id); |
| } catch (RemoteException ex) { |
| // system process is dead if we're here. |
| } |
| } |
| }); |
| } else { |
| vetoButton.setVisibility(View.GONE); |
| } |
| 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); |
| } |
| |
| entry.row = row; |
| entry.content = content; |
| entry.expanded = expanded; |
| entry.largeIcon = largeIcon; |
| |
| return true; |
| } |
| |
| 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); |
| } |
| |
| /* |
| 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_NAVIGATION) != 0) ? "NAVIGATION" : "navigation"); |
| flagdbg.append(((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) ? "* " : " "); |
| flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back"); |
| flagdbg.append(((diff & StatusBarManager.DISABLE_BACK) != 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_NAVIGATION | StatusBarManager.DISABLE_BACK)) != 0) { |
| // the nav bar will take care of DISABLE_NAVIGATION and DISABLE_BACK |
| if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state); |
| |
| if ((state & StatusBarManager.DISABLE_NAVIGATION) != 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_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) { |
| 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, 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; |
| } |
| |
| 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 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()); |
| } |
| |
| 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() { |
| 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")); |
| } |
| } |
| } |
| |
| // The user is not allowed to get stuck without navigation UI. Upon the slightest user |
| // interaction we bring the navigation back. |
| public void userActivity() { |
| if (0 != (mSystemUiVisibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)) { |
| try { |
| mBarService.setSystemUiVisibility( |
| mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); |
| } catch (RemoteException ex) { |
| // weep softly |
| } |
| } |
| } |
| |
| public void toggleRecentApps() { |
| int msg = (mRecentsPanel.getVisibility() == View.GONE) |
| ? MSG_OPEN_RECENTS_PANEL : MSG_CLOSE_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() { |
| public void onClick(View v) { |
| try { |
| mBarService.onClearAllNotifications(); |
| } catch (RemoteException ex) { |
| // system process is dead if we're here. |
| } |
| animateCollapse(); |
| } |
| }; |
| |
| private View.OnClickListener mSettingsButtonListener = new View.OnClickListener() { |
| public void onClick(View v) { |
| 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; |
| } |
| } |
| } |
| |