| /* |
| * Copyright (C) 2007 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.server.status; |
| |
| import com.android.internal.R; |
| import com.android.internal.util.CharSequences; |
| |
| import android.app.ActivityManagerNative; |
| import android.app.Dialog; |
| import android.app.IStatusBar; |
| 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.PackageManager; |
| import android.content.res.Resources; |
| import android.graphics.PixelFormat; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.SystemClock; |
| import android.provider.Telephony; |
| import android.util.Log; |
| import android.view.Display; |
| import android.view.Gravity; |
| 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.Window; |
| import android.view.WindowManager; |
| import android.view.WindowManagerImpl; |
| import android.view.animation.Animation; |
| import android.view.animation.AnimationUtils; |
| import android.widget.LinearLayout; |
| import android.widget.RemoteViews; |
| import android.widget.ScrollView; |
| import android.widget.TextView; |
| import android.widget.FrameLayout; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Set; |
| |
| |
| /** |
| * The public (ok, semi-public) service for the status bar. |
| * <p> |
| * This interesting thing to note about this class is that most of the methods that |
| * are called from other classes just post a message, and everything else is batched |
| * and coalesced into a series of calls to methods that all start with "perform." |
| * There are two reasons for this. The first is that some of the methods (activate/deactivate) |
| * are on IStatusBar, so they're called from the thread pool and they need to make their |
| * way onto the UI thread. The second is that the message queue is stopped while animations |
| * are happening in order to make for smoother transitions. |
| * <p> |
| * Each icon is either an icon or an icon and a notification. They're treated mostly |
| * separately throughout the code, although they both use the same key, which is assigned |
| * when they are created. |
| */ |
| public class StatusBarService extends IStatusBar.Stub |
| { |
| static final String TAG = "StatusBar"; |
| static final boolean DEBUG = false; |
| static final boolean SPEW = false; |
| static final boolean DBG = false; |
| |
| 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 OP_ADD_ICON = 1; |
| private static final int OP_UPDATE_ICON = 2; |
| private static final int OP_REMOVE_ICON = 3; |
| private static final int OP_SET_VISIBLE = 4; |
| private static final int OP_EXPAND = 5; |
| private static final int OP_TOGGLE = 6; |
| private static final int OP_DISABLE = 7; |
| private class PendingOp { |
| IBinder key; |
| int code; |
| IconData iconData; |
| NotificationData notificationData; |
| boolean visible; |
| int integer; |
| } |
| |
| private class DisableRecord implements IBinder.DeathRecipient { |
| String pkg; |
| int what; |
| IBinder token; |
| |
| public void binderDied() { |
| Log.i(TAG, "binder died for pkg=" + pkg); |
| disable(0, token, pkg); |
| } |
| } |
| |
| public interface NotificationCallbacks { |
| void onSetDisabled(int status); |
| void onClearAll(); |
| void onNotificationClick(String pkg, int id); |
| void onPanelRevealed(); |
| } |
| |
| private class ExpandedDialog extends Dialog { |
| ExpandedDialog(Context context) { |
| super(context, com.android.internal.R.style.Theme_Light_NoTitleBar); |
| } |
| |
| @Override |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| boolean down = event.getAction() == KeyEvent.ACTION_DOWN; |
| switch (event.getKeyCode()) { |
| case KeyEvent.KEYCODE_BACK: |
| if (down) { |
| StatusBarService.this.deactivate(); |
| } |
| return true; |
| } |
| return super.dispatchKeyEvent(event); |
| } |
| } |
| |
| final Context mContext; |
| final Display mDisplay; |
| StatusBarView mStatusBarView; |
| int mPixelFormat; |
| H mHandler = new H(); |
| ArrayList<PendingOp> mQueue = new ArrayList<PendingOp>(); |
| NotificationCallbacks mNotificationCallbacks; |
| |
| // All accesses to mIconMap and mNotificationData are syncronized on those objects, |
| // but this is only so dump() can work correctly. Modifying these outside of the UI |
| // thread will not work, there are places in the code that unlock and reaquire between |
| // reads and require them to not be modified. |
| |
| // icons |
| HashMap<IBinder,StatusBarIcon> mIconMap = new HashMap<IBinder,StatusBarIcon>(); |
| ArrayList<StatusBarIcon> mIconList = new ArrayList<StatusBarIcon>(); |
| String[] mRightIconSlots; |
| StatusBarIcon[] mRightIcons; |
| LinearLayout mIcons; |
| IconMerger mNotificationIcons; |
| LinearLayout mStatusIcons; |
| StatusBarIcon mMoreIcon; |
| private UninstallReceiver mUninstallReceiver; |
| |
| // expanded notifications |
| NotificationViewList mNotificationData = new NotificationViewList(); |
| Dialog mExpandedDialog; |
| ExpandedView mExpandedView; |
| WindowManager.LayoutParams mExpandedParams; |
| ScrollView mScrollView; |
| View mNotificationLinearLayout; |
| TextView mOngoingTitle; |
| LinearLayout mOngoingItems; |
| TextView mLatestTitle; |
| LinearLayout mLatestItems; |
| TextView mNoNotificationsTitle; |
| TextView mSpnLabel; |
| TextView mPlmnLabel; |
| TextView mClearButton; |
| CloseDragHandle mCloseView; |
| int[] mCloseLocation = new int[2]; |
| boolean mExpanded; |
| boolean mExpandedVisible; |
| |
| // the date view |
| DateView mDateView; |
| |
| // the tracker view |
| TrackingView mTrackingView; |
| WindowManager.LayoutParams mTrackingParams; |
| int mTrackingPosition; |
| |
| // ticker |
| private Ticker mTicker; |
| private View mTickerView; |
| private boolean mTicking; |
| |
| // Tracking finger for opening/closing. |
| boolean mTracking; |
| VelocityTracker mVelocityTracker; |
| |
| static final int ANIM_FRAME_DURATION = (1000/60); |
| |
| boolean mAnimating; |
| long mCurAnimationTime; |
| float mDisplayHeight; |
| float mAnimY; |
| float mAnimVel; |
| float mAnimAccel; |
| long mAnimLastTime; |
| boolean mAnimatingReveal = false; |
| int mViewDelta; |
| int[] mAbsPos = new int[2]; |
| |
| // for disabling the status bar |
| ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); |
| int mDisabled = 0; |
| |
| /** |
| * Construct the service, add the status bar view to the window manager |
| */ |
| public StatusBarService(Context context) { |
| mContext = context; |
| mDisplay = ((WindowManager)context.getSystemService( |
| Context.WINDOW_SERVICE)).getDefaultDisplay(); |
| makeStatusBarView(context); |
| mUninstallReceiver = new UninstallReceiver(); |
| } |
| |
| public void setNotificationCallbacks(NotificationCallbacks listener) { |
| mNotificationCallbacks = listener; |
| } |
| |
| // ================================================================================ |
| // Constructing the view |
| // ================================================================================ |
| private void makeStatusBarView(Context context) { |
| Resources res = context.getResources(); |
| mRightIconSlots = res.getStringArray(com.android.internal.R.array.status_bar_icon_order); |
| mRightIcons = new StatusBarIcon[mRightIconSlots.length]; |
| |
| ExpandedView expanded = (ExpandedView)View.inflate(context, |
| com.android.internal.R.layout.status_bar_expanded, null); |
| expanded.mService = this; |
| StatusBarView sb = (StatusBarView)View.inflate(context, |
| com.android.internal.R.layout.status_bar, null); |
| sb.mService = this; |
| |
| // figure out which pixel-format to use for the status bar. |
| mPixelFormat = PixelFormat.TRANSLUCENT; |
| Drawable bg = sb.getBackground(); |
| if (bg != null) { |
| mPixelFormat = bg.getOpacity(); |
| } |
| |
| mStatusBarView = sb; |
| mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons); |
| mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons); |
| mNotificationIcons.service = this; |
| mIcons = (LinearLayout)sb.findViewById(R.id.icons); |
| mTickerView = sb.findViewById(R.id.ticker); |
| mDateView = (DateView)sb.findViewById(R.id.date); |
| |
| mExpandedDialog = new ExpandedDialog(context); |
| mExpandedView = expanded; |
| mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle); |
| mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems); |
| mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle); |
| mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems); |
| mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle); |
| mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button); |
| mClearButton.setOnClickListener(mClearButtonListener); |
| mSpnLabel = (TextView)expanded.findViewById(R.id.spnLabel); |
| mPlmnLabel = (TextView)expanded.findViewById(R.id.plmnLabel); |
| mScrollView = (ScrollView)expanded.findViewById(R.id.scroll); |
| mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout); |
| |
| mOngoingTitle.setVisibility(View.GONE); |
| mLatestTitle.setVisibility(View.GONE); |
| |
| mTicker = new MyTicker(context, sb); |
| |
| TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText); |
| tickerView.mTicker = mTicker; |
| |
| mTrackingView = (TrackingView)View.inflate(context, |
| com.android.internal.R.layout.status_bar_tracking, null); |
| mTrackingView.mService = this; |
| mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close); |
| mCloseView.mService = this; |
| |
| // add the more icon for the notifications |
| IconData moreData = IconData.makeIcon(null, context.getPackageName(), |
| R.drawable.stat_notify_more, 0, 42); |
| mMoreIcon = new StatusBarIcon(context, moreData, mNotificationIcons); |
| mMoreIcon.view.setId(R.drawable.stat_notify_more); |
| mNotificationIcons.moreIcon = mMoreIcon; |
| mNotificationIcons.addView(mMoreIcon.view); |
| |
| // set the inital view visibility |
| setAreThereNotifications(); |
| mDateView.setVisibility(View.INVISIBLE); |
| |
| // before we register for broadcasts |
| mPlmnLabel.setText(R.string.lockscreen_carrier_default); |
| mPlmnLabel.setVisibility(View.VISIBLE); |
| mSpnLabel.setText(""); |
| mSpnLabel.setVisibility(View.GONE); |
| |
| // receive broadcasts |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); |
| filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); |
| filter.addAction(Telephony.Intents.SPN_STRINGS_UPDATED_ACTION); |
| context.registerReceiver(mBroadcastReceiver, filter); |
| } |
| |
| public void systemReady() { |
| final StatusBarView view = mStatusBarView; |
| WindowManager.LayoutParams lp = new WindowManager.LayoutParams( |
| ViewGroup.LayoutParams.FILL_PARENT, |
| view.getContext().getResources().getDimensionPixelSize( |
| com.android.internal.R.dimen.status_bar_height), |
| WindowManager.LayoutParams.TYPE_STATUS_BAR, |
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| |
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING, |
| mPixelFormat); |
| lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; |
| lp.setTitle("StatusBar"); |
| lp.windowAnimations = R.style.Animation_StatusBar; |
| |
| WindowManagerImpl.getDefault().addView(view, lp); |
| } |
| |
| // ================================================================================ |
| // From IStatusBar |
| // ================================================================================ |
| public void activate() { |
| enforceExpandStatusBar(); |
| addPendingOp(OP_EXPAND, null, true); |
| } |
| |
| public void deactivate() { |
| enforceExpandStatusBar(); |
| addPendingOp(OP_EXPAND, null, false); |
| } |
| |
| public void toggle() { |
| enforceExpandStatusBar(); |
| addPendingOp(OP_TOGGLE, null, false); |
| } |
| |
| public void disable(int what, IBinder token, String pkg) { |
| enforceStatusBar(); |
| synchronized (mNotificationCallbacks) { |
| // This is a little gross, but I think it's safe as long as nobody else |
| // synchronizes on mNotificationCallbacks. It's important that the the callback |
| // and the pending op get done in the correct order and not interleaved with |
| // other calls, otherwise they'll get out of sync. |
| int net; |
| synchronized (mDisableRecords) { |
| manageDisableListLocked(what, token, pkg); |
| net = gatherDisableActionsLocked(); |
| mNotificationCallbacks.onSetDisabled(net); |
| } |
| addPendingOp(OP_DISABLE, net); |
| } |
| } |
| |
| public IBinder addIcon(String slot, String iconPackage, int iconId, int iconLevel) { |
| enforceStatusBar(); |
| return addIcon(IconData.makeIcon(slot, iconPackage, iconId, iconLevel, 0), null); |
| } |
| |
| public void updateIcon(IBinder key, |
| String slot, String iconPackage, int iconId, int iconLevel) { |
| enforceStatusBar(); |
| updateIcon(key, IconData.makeIcon(slot, iconPackage, iconId, iconLevel, 0), null); |
| } |
| |
| public void removeIcon(IBinder key) { |
| enforceStatusBar(); |
| addPendingOp(OP_REMOVE_ICON, key, null, null, -1); |
| } |
| |
| private void enforceStatusBar() { |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.STATUS_BAR, |
| "StatusBarService"); |
| } |
| |
| private void enforceExpandStatusBar() { |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.EXPAND_STATUS_BAR, |
| "StatusBarService"); |
| } |
| |
| // ================================================================================ |
| // Can be called from any thread |
| // ================================================================================ |
| public IBinder addIcon(IconData data, NotificationData n) { |
| int slot; |
| // assert early-on if they using a slot that doesn't exist. |
| if (data != null && n == null) { |
| slot = getRightIconIndex(data.slot); |
| if (slot < 0) { |
| throw new SecurityException("invalid status bar icon slot: " |
| + (data.slot != null ? "'" + data.slot + "'" : "null")); |
| } |
| } else { |
| slot = -1; |
| } |
| IBinder key = new Binder(); |
| addPendingOp(OP_ADD_ICON, key, data, n, -1); |
| return key; |
| } |
| |
| public void updateIcon(IBinder key, IconData data, NotificationData n) { |
| addPendingOp(OP_UPDATE_ICON, key, data, n, -1); |
| } |
| |
| public void setIconVisibility(IBinder key, boolean visible) { |
| addPendingOp(OP_SET_VISIBLE, key, visible); |
| } |
| |
| private void addPendingOp(int code, IBinder key, IconData data, NotificationData n, int i) { |
| synchronized (mQueue) { |
| PendingOp op = new PendingOp(); |
| op.key = key; |
| op.code = code; |
| op.iconData = data == null ? null : data.clone(); |
| op.notificationData = n; |
| op.integer = i; |
| mQueue.add(op); |
| if (mQueue.size() == 1) { |
| mHandler.sendEmptyMessage(2); |
| } |
| } |
| } |
| |
| private void addPendingOp(int code, IBinder key, boolean visible) { |
| synchronized (mQueue) { |
| PendingOp op = new PendingOp(); |
| op.key = key; |
| op.code = code; |
| op.visible = visible; |
| mQueue.add(op); |
| if (mQueue.size() == 1) { |
| mHandler.sendEmptyMessage(1); |
| } |
| } |
| } |
| |
| private void addPendingOp(int code, int integer) { |
| synchronized (mQueue) { |
| PendingOp op = new PendingOp(); |
| op.code = code; |
| op.integer = integer; |
| mQueue.add(op); |
| if (mQueue.size() == 1) { |
| mHandler.sendEmptyMessage(1); |
| } |
| } |
| } |
| |
| // lock on mDisableRecords |
| void manageDisableListLocked(int what, IBinder token, String pkg) { |
| if (SPEW) { |
| Log.d(TAG, "manageDisableList what=0x" + Integer.toHexString(what) |
| + " pkg=" + pkg); |
| } |
| // update the list |
| synchronized (mDisableRecords) { |
| final int N = mDisableRecords.size(); |
| DisableRecord tok = null; |
| int i; |
| for (i=0; i<N; i++) { |
| DisableRecord t = mDisableRecords.get(i); |
| if (t.token == token) { |
| tok = t; |
| break; |
| } |
| } |
| if (what == 0 || !token.isBinderAlive()) { |
| if (tok != null) { |
| mDisableRecords.remove(i); |
| } |
| } else { |
| if (tok == null) { |
| tok = new DisableRecord(); |
| try { |
| token.linkToDeath(tok, 0); |
| } |
| catch (RemoteException ex) { |
| return; // give up |
| } |
| mDisableRecords.add(tok); |
| } |
| tok.what = what; |
| tok.token = token; |
| tok.pkg = pkg; |
| } |
| } |
| } |
| |
| // lock on mDisableRecords |
| int gatherDisableActionsLocked() { |
| final int N = mDisableRecords.size(); |
| // gather the new net flags |
| int net = 0; |
| for (int i=0; i<N; i++) { |
| net |= mDisableRecords.get(i).what; |
| } |
| return net; |
| } |
| |
| private int getRightIconIndex(String slot) { |
| final int N = mRightIconSlots.length; |
| for (int i=0; i<N; i++) { |
| if (mRightIconSlots[i].equals(slot)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| // ================================================================================ |
| // Always called from UI thread |
| // ================================================================================ |
| /** |
| * All changes to the status bar and notifications funnel through here and are batched. |
| */ |
| private class H extends Handler { |
| public void handleMessage(Message m) { |
| if (m.what == MSG_ANIMATE) { |
| doAnimation(); |
| return; |
| } |
| if (m.what == MSG_ANIMATE_REVEAL) { |
| doRevealAnimation(); |
| return; |
| } |
| synchronized (mQueue) { |
| boolean wasExpanded = mExpanded; |
| |
| // for each one in the queue, find all of the ones with the same key |
| // and collapse that down into a final op and/or call to setVisibility, etc |
| boolean expand = wasExpanded; |
| boolean doExpand = false; |
| boolean doDisable = false; |
| int disableWhat = 0; |
| int N = mQueue.size(); |
| while (N > 0) { |
| PendingOp op = mQueue.get(0); |
| boolean doOp = false; |
| boolean visible = false; |
| boolean doVisibility = false; |
| if (op.code == OP_SET_VISIBLE) { |
| doVisibility = true; |
| visible = op.visible; |
| } |
| else if (op.code == OP_EXPAND) { |
| doExpand = true; |
| expand = op.visible; |
| } |
| else if (op.code == OP_TOGGLE) { |
| doExpand = true; |
| expand = !expand; |
| } |
| else { |
| doOp = true; |
| } |
| |
| if (alwaysHandle(op.code)) { |
| // coalesce these |
| for (int i=1; i<N; i++) { |
| PendingOp o = mQueue.get(i); |
| if (!alwaysHandle(o.code) && o.key == op.key) { |
| if (o.code == OP_SET_VISIBLE) { |
| visible = o.visible; |
| doVisibility = true; |
| } |
| else if (o.code == OP_EXPAND) { |
| expand = o.visible; |
| doExpand = true; |
| } |
| else { |
| op.code = o.code; |
| op.iconData = o.iconData; |
| op.notificationData = o.notificationData; |
| } |
| mQueue.remove(i); |
| i--; |
| N--; |
| } |
| } |
| } |
| |
| mQueue.remove(0); |
| N--; |
| |
| if (doOp) { |
| switch (op.code) { |
| case OP_ADD_ICON: |
| case OP_UPDATE_ICON: |
| performAddUpdateIcon(op.key, op.iconData, op.notificationData); |
| break; |
| case OP_REMOVE_ICON: |
| performRemoveIcon(op.key); |
| break; |
| case OP_DISABLE: |
| doDisable = true; |
| disableWhat = op.integer; |
| break; |
| } |
| } |
| if (doVisibility && op.code != OP_REMOVE_ICON) { |
| performSetIconVisibility(op.key, visible); |
| } |
| } |
| |
| if (mQueue.size() != 0) { |
| throw new RuntimeException("Assertion failed: mQueue.size=" + mQueue.size()); |
| } |
| if (doExpand) { |
| // this is last so that we capture all of the pending changes before doing it |
| if (expand) { |
| animateExpand(); |
| } else { |
| animateCollapse(); |
| } |
| } |
| if (doDisable) { |
| performDisableActions(disableWhat); |
| } |
| } |
| } |
| } |
| |
| private boolean alwaysHandle(int code) { |
| return code == OP_DISABLE; |
| } |
| |
| /* private */ void performAddUpdateIcon(IBinder key, IconData data, NotificationData n) |
| throws StatusBarException { |
| if (DBG) { |
| Log.d(TAG, "performAddUpdateIcon icon=" + data + " notification=" + n + " key=" + key); |
| } |
| // notification |
| if (n != null) { |
| StatusBarNotification notification = getNotification(key); |
| NotificationData oldData = null; |
| if (notification == null) { |
| // add |
| notification = new StatusBarNotification(); |
| notification.key = key; |
| notification.data = n; |
| synchronized (mNotificationData) { |
| mNotificationData.add(notification); |
| } |
| addNotificationView(notification); |
| setAreThereNotifications(); |
| } else { |
| // update |
| oldData = notification.data; |
| notification.data = n; |
| updateNotificationView(notification, oldData); |
| } |
| // Show the ticker if one is requested, and the text is different |
| // than the currently displayed ticker. 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.tickerText != null && mStatusBarView.getWindowToken() != null |
| && (oldData == null |
| || oldData.tickerText == null |
| || !CharSequences.equals(oldData.tickerText, n.tickerText))) { |
| if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { |
| mTicker.addEntry(n, StatusBarIcon.getIcon(mContext, data), n.tickerText); |
| } |
| } |
| } |
| |
| // icon |
| synchronized (mIconMap) { |
| StatusBarIcon icon = mIconMap.get(key); |
| if (icon == null) { |
| // add |
| LinearLayout v = n == null ? mStatusIcons : mNotificationIcons; |
| |
| icon = new StatusBarIcon(mContext, data, v); |
| mIconMap.put(key, icon); |
| mIconList.add(icon); |
| |
| if (n == null) { |
| int slotIndex = getRightIconIndex(data.slot); |
| StatusBarIcon[] rightIcons = mRightIcons; |
| if (rightIcons[slotIndex] == null) { |
| int pos = 0; |
| for (int i=mRightIcons.length-1; i>slotIndex; i--) { |
| StatusBarIcon ic = rightIcons[i]; |
| if (ic != null) { |
| pos++; |
| } |
| } |
| rightIcons[slotIndex] = icon; |
| mStatusIcons.addView(icon.view, pos); |
| } else { |
| Log.e(TAG, "duplicate icon in slot " + slotIndex + "/" + data.slot); |
| mIconMap.remove(key); |
| mIconList.remove(icon); |
| return ; |
| } |
| } else { |
| int iconIndex = mNotificationData.getIconIndex(n); |
| mNotificationIcons.addView(icon.view, iconIndex); |
| } |
| } else { |
| if (n == null) { |
| // right hand side icons -- these don't reorder |
| icon.update(mContext, data); |
| } else { |
| // remove old |
| ViewGroup parent = (ViewGroup)icon.view.getParent(); |
| parent.removeView(icon.view); |
| // add new |
| icon.update(mContext, data); |
| int iconIndex = mNotificationData.getIconIndex(n); |
| mNotificationIcons.addView(icon.view, iconIndex); |
| } |
| } |
| } |
| } |
| |
| /* private */ void performSetIconVisibility(IBinder key, boolean visible) { |
| synchronized (mIconMap) { |
| if (DBG) { |
| Log.d(TAG, "performSetIconVisibility key=" + key + " visible=" + visible); |
| } |
| StatusBarIcon icon = mIconMap.get(key); |
| icon.view.setVisibility(visible ? View.VISIBLE : View.GONE); |
| } |
| } |
| |
| /* private */ void performRemoveIcon(IBinder key) { |
| synchronized (this) { |
| if (DBG) { |
| Log.d(TAG, "performRemoveIcon key=" + key); |
| } |
| StatusBarIcon icon = mIconMap.remove(key); |
| mIconList.remove(icon); |
| if (icon != null) { |
| ViewGroup parent = (ViewGroup)icon.view.getParent(); |
| parent.removeView(icon.view); |
| int slotIndex = getRightIconIndex(icon.mData.slot); |
| if (slotIndex >= 0) { |
| mRightIcons[slotIndex] = null; |
| } |
| } |
| StatusBarNotification notification = getNotification(key); |
| if (notification != null) { |
| removeNotificationView(notification); |
| synchronized (mNotificationData) { |
| mNotificationData.remove(notification); |
| } |
| setAreThereNotifications(); |
| } |
| } |
| } |
| |
| int getIconNumberForView(View v) { |
| synchronized (mIconMap) { |
| StatusBarIcon icon = null; |
| final int N = mIconList.size(); |
| for (int i=0; i<N; i++) { |
| StatusBarIcon ic = mIconList.get(i); |
| if (ic.view == v) { |
| icon = ic; |
| break; |
| } |
| } |
| if (icon != null) { |
| return icon.getNumber(); |
| } else { |
| return -1; |
| } |
| } |
| } |
| |
| |
| StatusBarNotification getNotification(IBinder key) { |
| synchronized (mNotificationData) { |
| return mNotificationData.get(key); |
| } |
| } |
| |
| 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); |
| } |
| }; |
| |
| View makeNotificationView(StatusBarNotification notification, ViewGroup parent) { |
| NotificationData n = notification.data; |
| 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(com.android.internal.R.layout.status_bar_latest_event, parent, false); |
| |
| // bind the click event to the content area |
| ViewGroup content = (ViewGroup)row.findViewById(com.android.internal.R.id.content); |
| content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); |
| content.setOnFocusChangeListener(mFocusChangeListener); |
| PendingIntent contentIntent = n.contentIntent; |
| if (contentIntent != null) { |
| content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.id)); |
| } |
| |
| View child = null; |
| Exception exception = null; |
| try { |
| child = remoteViews.apply(mContext, content); |
| } |
| catch (RuntimeException e) { |
| exception = e; |
| } |
| if (child == null) { |
| Log.e(TAG, "couldn't inflate view for package " + n.pkg, exception); |
| return null; |
| } |
| content.addView(child); |
| |
| row.setDrawingCacheEnabled(true); |
| |
| notification.view = row; |
| notification.contentView = child; |
| |
| return row; |
| } |
| |
| void addNotificationView(StatusBarNotification notification) { |
| if (notification.view != null) { |
| throw new RuntimeException("Assertion failed: notification.view=" |
| + notification.view); |
| } |
| |
| LinearLayout parent = notification.data.ongoingEvent ? mOngoingItems : mLatestItems; |
| |
| View child = makeNotificationView(notification, parent); |
| if (child == null) { |
| return ; |
| } |
| |
| int index = mNotificationData.getExpandedIndex(notification); |
| parent.addView(child, index); |
| } |
| |
| /** |
| * Remove the old one and put the new one in its place. |
| * @param notification the notification |
| */ |
| void updateNotificationView(StatusBarNotification notification, NotificationData oldData) { |
| NotificationData n = notification.data; |
| if (oldData != null && n != null |
| && n.contentView != null && oldData.contentView != null |
| && n.contentView.getPackage() != null |
| && oldData.contentView.getPackage() != null |
| && oldData.contentView.getPackage().equals(n.contentView.getPackage()) |
| && oldData.contentView.getLayoutId() == n.contentView.getLayoutId()) { |
| mNotificationData.update(notification); |
| try { |
| n.contentView.reapply(mContext, notification.contentView); |
| |
| // update the contentIntent |
| ViewGroup content = (ViewGroup)notification.view.findViewById( |
| com.android.internal.R.id.content); |
| PendingIntent contentIntent = n.contentIntent; |
| if (contentIntent != null) { |
| content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.id)); |
| } |
| } |
| catch (RuntimeException e) { |
| // It failed to add cleanly. Log, and remove the view from the panel. |
| Log.w(TAG, "couldn't reapply views for package " + n.contentView.getPackage(), e); |
| removeNotificationView(notification); |
| } |
| } else { |
| mNotificationData.update(notification); |
| removeNotificationView(notification); |
| addNotificationView(notification); |
| } |
| setAreThereNotifications(); |
| } |
| |
| void removeNotificationView(StatusBarNotification notification) { |
| View v = notification.view; |
| if (v != null) { |
| ViewGroup parent = (ViewGroup)v.getParent(); |
| parent.removeView(v); |
| notification.view = null; |
| } |
| } |
| |
| private void setAreThereNotifications() { |
| boolean ongoing = mOngoingItems.getChildCount() != 0; |
| boolean latest = mLatestItems.getChildCount() != 0; |
| |
| if (mNotificationData.hasClearableItems()) { |
| mClearButton.setVisibility(View.VISIBLE); |
| } else { |
| mClearButton.setVisibility(View.INVISIBLE); |
| } |
| |
| mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE); |
| mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE); |
| |
| if (ongoing || latest) { |
| mNoNotificationsTitle.setVisibility(View.GONE); |
| } else { |
| mNoNotificationsTitle.setVisibility(View.VISIBLE); |
| } |
| } |
| |
| private void makeExpandedVisible() { |
| if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); |
| if (mExpandedVisible) { |
| return; |
| } |
| mExpandedVisible = true; |
| panelSlightlyVisible(true); |
| |
| updateExpandedViewPos(EXPANDED_LEAVE_ALONE); |
| mExpandedDialog.show(); |
| mExpandedView.requestFocus(View.FOCUS_FORWARD); |
| mTrackingView.setVisibility(View.VISIBLE); |
| |
| if (!mTicking) { |
| setDateViewVisibility(true, com.android.internal.R.anim.fade_in); |
| } |
| } |
| |
| void animateExpand() { |
| if (SPEW) Log.d(TAG, "Animate expand: expanded=" + mExpanded); |
| if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { |
| return ; |
| } |
| if (mExpanded) { |
| return; |
| } |
| |
| prepareTracking(0); |
| performFling(0, 2000.0f, true); |
| } |
| |
| void animateCollapse() { |
| if (SPEW) Log.d(TAG, "Animate collapse: expanded=" + mExpanded |
| + " expanded visible=" + mExpandedVisible); |
| |
| if (!mExpandedVisible) { |
| return; |
| } |
| |
| prepareTracking(mDisplay.getHeight()-1); |
| performFling(mDisplay.getHeight()-1, -2000.0f, true); |
| } |
| |
| void performExpand() { |
| if (SPEW) Log.d(TAG, "Perform expand: expanded=" + mExpanded); |
| if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { |
| return ; |
| } |
| if (mExpanded) { |
| return; |
| } |
| |
| // It seems strange to sometimes not expand... |
| if (false) { |
| synchronized (mNotificationData) { |
| if (mNotificationData.size() == 0) { |
| return; |
| } |
| } |
| } |
| |
| mExpanded = true; |
| makeExpandedVisible(); |
| updateExpandedViewPos(EXPANDED_FULL_OPEN); |
| |
| if (false) postStartTracing(); |
| } |
| |
| void performCollapse() { |
| if (SPEW) Log.d(TAG, "Perform collapse: expanded=" + mExpanded |
| + " expanded visible=" + mExpandedVisible); |
| |
| if (!mExpandedVisible) { |
| return; |
| } |
| mExpandedVisible = false; |
| panelSlightlyVisible(false); |
| mExpandedDialog.hide(); |
| mTrackingView.setVisibility(View.GONE); |
| |
| if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { |
| setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); |
| } |
| setDateViewVisibility(false, com.android.internal.R.anim.fade_out); |
| |
| if (!mExpanded) { |
| return; |
| } |
| mExpanded = false; |
| } |
| |
| void doAnimation() { |
| if (mAnimating) { |
| if (SPEW) Log.d(TAG, "doAnimation"); |
| if (SPEW) Log.d(TAG, "doAnimation before mAnimY=" + mAnimY); |
| incrementAnim(); |
| if (SPEW) Log.d(TAG, "doAnimation after mAnimY=" + mAnimY); |
| if (mAnimY >= mDisplay.getHeight()-1) { |
| if (SPEW) Log.d(TAG, "Animation completed to expanded state."); |
| mAnimating = false; |
| updateExpandedViewPos(EXPANDED_FULL_OPEN); |
| performExpand(); |
| } |
| else if (mAnimY < mStatusBarView.getHeight()) { |
| if (SPEW) Log.d(TAG, "Animation completed to collapsed state."); |
| mAnimating = false; |
| 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 |
| //Log.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) { |
| mTracking = true; |
| mVelocityTracker = VelocityTracker.obtain(); |
| boolean opening = !mExpanded; |
| if (!mExpanded) { |
| mAnimAccel = 2000.0f; |
| mAnimVel = 200; |
| 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); |
| } else { |
| // it's open, close it? |
| if (mAnimating) { |
| mAnimating = false; |
| mHandler.removeMessages(MSG_ANIMATE); |
| } |
| } |
| if (opening) { |
| makeExpandedVisible(); |
| } else { |
| updateExpandedViewPos(y + mViewDelta); |
| } |
| } |
| |
| void performFling(int y, float vel, boolean always) { |
| mAnimatingReveal = false; |
| mDisplayHeight = mDisplay.getHeight(); |
| |
| mAnimY = y; |
| mAnimVel = vel; |
| |
| //Log.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); |
| |
| if (mExpanded) { |
| if (!always && ( |
| vel > 200.0f |
| || (y > (mDisplayHeight-25) && vel > -200.0f))) { |
| // We are expanded, but they didn't move sufficiently to cause |
| // us to retract. Animate back to the expanded position. |
| mAnimAccel = 2000.0f; |
| if (vel < 0) { |
| mAnimVel = 0; |
| } |
| } |
| else { |
| // We are expanded and are now going to animate away. |
| mAnimAccel = -2000.0f; |
| if (vel > 0) { |
| mAnimVel = 0; |
| } |
| } |
| } else { |
| if (always || ( |
| vel > 200.0f |
| || (y > (mDisplayHeight/2) && vel > -200.0f))) { |
| // We are collapsed, and they moved enough to allow us to |
| // expand. Animate in the notifications. |
| mAnimAccel = 2000.0f; |
| 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 = -2000.0f; |
| if (vel > 0) { |
| mAnimVel = 0; |
| } |
| } |
| } |
| //Log.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) Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event); |
| |
| if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { |
| return true; |
| } |
| |
| final int statusBarSize = mStatusBarView.getHeight(); |
| final int hitSize = statusBarSize*2; |
| if (event.getAction() == MotionEvent.ACTION_DOWN) { |
| int y = (int)event.getRawY(); |
| |
| if (!mExpanded) { |
| mViewDelta = statusBarSize - y; |
| } else { |
| mTrackingView.getLocationOnScreen(mAbsPos); |
| mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y; |
| } |
| if ((!mExpanded && y < hitSize) || |
| (mExpanded && y > (mDisplay.getHeight()-hitSize))) { |
| prepareTracking(y); |
| mVelocityTracker.addMovement(event); |
| } |
| } else if (mTracking) { |
| mVelocityTracker.addMovement(event); |
| final int minY = statusBarSize + mCloseView.getHeight(); |
| if (event.getAction() == MotionEvent.ACTION_MOVE) { |
| int y = (int)event.getRawY(); |
| if (mAnimatingReveal && y < minY) { |
| // nothing |
| } else { |
| mAnimatingReveal = false; |
| updateExpandedViewPos(y + mViewDelta); |
| } |
| } else if (event.getAction() == MotionEvent.ACTION_UP) { |
| mVelocityTracker.computeCurrentVelocity(1000); |
| |
| float yVel = mVelocityTracker.getYVelocity(); |
| boolean negative = yVel < 0; |
| |
| float xVel = mVelocityTracker.getXVelocity(); |
| if (xVel < 0) { |
| xVel = -xVel; |
| } |
| if (xVel > 150.0f) { |
| xVel = 150.0f; // limit how much we care about the x axis |
| } |
| |
| float vel = (float)Math.hypot(yVel, xVel); |
| if (negative) { |
| vel = -vel; |
| } |
| |
| performFling((int)event.getRawY(), vel, false); |
| } |
| |
| } |
| return false; |
| } |
| |
| private class Launcher implements View.OnClickListener { |
| private PendingIntent mIntent; |
| private String mPkg; |
| private int mId; |
| |
| Launcher(PendingIntent intent, String pkg, int id) { |
| mIntent = intent; |
| mPkg = pkg; |
| 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(); |
| } catch (RemoteException e) { |
| } |
| try { |
| mIntent.send(); |
| mNotificationCallbacks.onNotificationClick(mPkg, mId); |
| } catch (PendingIntent.CanceledException e) { |
| // the stack trace isn't very helpful here. Just log the exception message. |
| Log.w(TAG, "Sending contentIntent failed: " + e); |
| } |
| deactivate(); |
| } |
| } |
| |
| private class MyTicker extends Ticker { |
| MyTicker(Context context, StatusBarView sb) { |
| super(context, sb); |
| } |
| |
| @Override |
| 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)); |
| if (mExpandedVisible) { |
| setDateViewVisibility(false, com.android.internal.R.anim.push_up_out); |
| } |
| } |
| |
| @Override |
| 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)); |
| if (mExpandedVisible) { |
| setDateViewVisibility(true, com.android.internal.R.anim.push_down_in); |
| } |
| } |
| |
| 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)); |
| if (mExpandedVisible) { |
| setDateViewVisibility(true, com.android.internal.R.anim.fade_in); |
| } |
| } |
| } |
| |
| 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() + ")"; |
| } |
| |
| protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) |
| != PackageManager.PERMISSION_GRANTED) { |
| pw.println("Permission Denial: can't dump StatusBar from from pid=" |
| + Binder.getCallingPid() |
| + ", uid=" + Binder.getCallingUid()); |
| return; |
| } |
| |
| synchronized (mQueue) { |
| 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(" mDisplayHeight=" + mDisplayHeight |
| + " mAnimatingReveal=" + mAnimatingReveal |
| + " mViewDelta=" + mViewDelta); |
| pw.println(" mDisplayHeight=" + mDisplayHeight); |
| final int N = mQueue.size(); |
| pw.println(" mQueue.size=" + N); |
| for (int i=0; i<N; i++) { |
| PendingOp op = mQueue.get(i); |
| pw.println(" [" + i + "] key=" + op.key + " code=" + op.code + " visible=" |
| + op.visible); |
| pw.println(" iconData=" + op.iconData); |
| pw.println(" notificationData=" + op.notificationData); |
| } |
| pw.println(" mExpandedParams: " + mExpandedParams); |
| pw.println(" mExpandedView: " + viewInfo(mExpandedView)); |
| pw.println(" mExpandedDialog: " + mExpandedDialog); |
| pw.println(" mTrackingParams: " + mTrackingParams); |
| pw.println(" mTrackingView: " + viewInfo(mTrackingView)); |
| pw.println(" mOngoingTitle: " + viewInfo(mOngoingTitle)); |
| pw.println(" mOngoingItems: " + viewInfo(mOngoingItems)); |
| pw.println(" mLatestTitle: " + viewInfo(mLatestTitle)); |
| pw.println(" mLatestItems: " + viewInfo(mLatestItems)); |
| 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.println("mNotificationLinearLayout: " + viewInfo(mNotificationLinearLayout)); |
| } |
| synchronized (mIconMap) { |
| final int N = mIconMap.size(); |
| pw.println(" mIconMap.size=" + N); |
| Set<IBinder> keys = mIconMap.keySet(); |
| int i=0; |
| for (IBinder key: keys) { |
| StatusBarIcon icon = mIconMap.get(key); |
| pw.println(" [" + i + "] key=" + key); |
| pw.println(" data=" + icon.mData); |
| i++; |
| } |
| } |
| synchronized (mNotificationData) { |
| int N = mNotificationData.ongoingCount(); |
| pw.println(" ongoingCount.size=" + N); |
| for (int i=0; i<N; i++) { |
| StatusBarNotification n = mNotificationData.getOngoing(i); |
| pw.println(" [" + i + "] key=" + n.key + " view=" + n.view); |
| pw.println(" data=" + n.data); |
| } |
| N = mNotificationData.latestCount(); |
| pw.println(" ongoingCount.size=" + N); |
| for (int i=0; i<N; i++) { |
| StatusBarNotification n = mNotificationData.getLatest(i); |
| pw.println(" [" + i + "] key=" + n.key + " view=" + n.view); |
| pw.println(" data=" + n.data); |
| } |
| } |
| synchronized (mDisableRecords) { |
| final int N = mDisableRecords.size(); |
| pw.println(" mDisableRecords.size=" + N |
| + " mDisabled=0x" + Integer.toHexString(mDisabled)); |
| for (int i=0; i<N; i++) { |
| DisableRecord tok = mDisableRecords.get(i); |
| pw.println(" [" + i + "] what=0x" + Integer.toHexString(tok.what) |
| + " pkg=" + tok.pkg + " token=" + tok.token); |
| } |
| } |
| |
| 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); |
| Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] |
| + ") " + mStatusBarView.getWidth() + "x" |
| + mStatusBarView.getHeight()); |
| mStatusBarView.debug(); |
| |
| mExpandedView.getLocationOnScreen(mAbsPos); |
| Log.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] |
| + ") " + mExpandedView.getWidth() + "x" |
| + mExpandedView.getHeight()); |
| mExpandedView.debug(); |
| |
| mTrackingView.getLocationOnScreen(mAbsPos); |
| Log.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] |
| + ") " + mTrackingView.getWidth() + "x" |
| + mTrackingView.getHeight()); |
| mTrackingView.debug(); |
| } |
| }); |
| } |
| } |
| |
| void onBarViewAttached() { |
| WindowManager.LayoutParams lp; |
| int pixelFormat; |
| Drawable bg; |
| |
| /// ---------- Tracking View -------------- |
| pixelFormat = PixelFormat.TRANSLUCENT; |
| bg = mTrackingView.getBackground(); |
| if (bg != null) { |
| pixelFormat = bg.getOpacity(); |
| } |
| |
| lp = new WindowManager.LayoutParams( |
| ViewGroup.LayoutParams.FILL_PARENT, |
| ViewGroup.LayoutParams.FILL_PARENT, |
| WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, |
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
| | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
| | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, |
| pixelFormat); |
| // lp.token = mStatusBarView.getWindowToken(); |
| lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; |
| lp.setTitle("TrackingView"); |
| mTrackingParams = lp; |
| |
| WindowManagerImpl.getDefault().addView(mTrackingView, lp); |
| } |
| |
| void onTrackingViewAttached() { |
| WindowManager.LayoutParams lp; |
| int pixelFormat; |
| Drawable bg; |
| |
| /// ---------- Expanded View -------------- |
| pixelFormat = PixelFormat.TRANSLUCENT; |
| if (false) { |
| bg = mExpandedView.getBackground(); |
| if (bg != null) { |
| pixelFormat = bg.getOpacity(); |
| } |
| } |
| |
| lp = mExpandedDialog.getWindow().getAttributes(); |
| lp.width = ViewGroup.LayoutParams.FILL_PARENT; |
| lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; |
| lp.x = 0; |
| lp.y = 0; |
| lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; |
| lp.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
| | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
| | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
| | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
| | WindowManager.LayoutParams.FLAG_DITHER; |
| lp.format = pixelFormat; |
| lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; |
| lp.setTitle("StatusBarExpanded"); |
| mExpandedDialog.getWindow().setAttributes(lp); |
| mExpandedDialog.getWindow().setFormat(pixelFormat); |
| mExpandedParams = lp; |
| |
| mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); |
| mExpandedDialog.setContentView(mExpandedView, |
| new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, |
| ViewGroup.LayoutParams.WRAP_CONTENT)); |
| mExpandedDialog.show(); |
| mExpandedDialog.hide(); |
| FrameLayout hack = (FrameLayout)mExpandedView.getParent(); |
| hack.setForeground(null); |
| } |
| |
| void setDateViewVisibility(boolean visible, int anim) { |
| mDateView.setUpdates(visible); |
| mDateView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); |
| mDateView.startAnimation(loadAnim(anim, null)); |
| } |
| |
| 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 updateExpandedViewPos(int expandedPosition) { |
| if (SPEW) { |
| Log.d(TAG, "updateExpandedViewPos before pos=" + expandedPosition |
| + " mTrackingParams.y=" + mTrackingParams.y |
| + " mTrackingPosition=" + mTrackingPosition); |
| } |
| |
| // If the expanded view is not visible, there is no reason to do |
| // any work. |
| if (!mExpandedVisible) { |
| return; |
| } |
| |
| // tracking view... |
| int h = mStatusBarView.getHeight(); |
| int disph = mDisplay.getHeight(); |
| 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); |
| |
| mCloseView.getLocationInWindow(mCloseLocation); |
| |
| if (mExpandedParams != null) { |
| mExpandedParams.y = pos + mTrackingView.getHeight() |
| - (mTrackingParams.height-mCloseLocation[1]) - mExpandedView.getHeight(); |
| int max = h; |
| if (mExpandedParams.y > max) { |
| mExpandedParams.y = max; |
| } |
| int min = mTrackingPosition; |
| if (mExpandedParams.y < min) { |
| mExpandedParams.y = min; |
| } |
| |
| /* |
| Log.d(TAG, "mTrackingPosition=" + mTrackingPosition |
| + " mTrackingView.height=" + mTrackingView.getHeight() |
| + " diff=" + (mTrackingPosition + mTrackingView.getHeight()) |
| + " h=" + h); |
| */ |
| panelSlightlyVisible((mTrackingPosition + mTrackingView.getHeight()) > h); |
| mExpandedDialog.getWindow().setAttributes(mExpandedParams); |
| } |
| |
| if (SPEW) { |
| Log.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition |
| + " mTrackingParams.y=" + mTrackingParams.y |
| + " mTrackingPosition=" + mTrackingPosition |
| + " mExpandedParams.y=" + mExpandedParams.y); |
| } |
| } |
| |
| void updateAvailableHeight() { |
| if (mExpandedView != null) { |
| int disph = mDisplay.getHeight(); |
| int h = mStatusBarView.getHeight(); |
| int max = disph - (mCloseView.getHeight() + h); |
| mExpandedView.setMaxHeight(max); |
| } |
| } |
| |
| /** |
| * 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) |
| */ |
| private boolean mPanelSlightlyVisible; |
| void panelSlightlyVisible(boolean visible) { |
| if (mPanelSlightlyVisible != visible) { |
| mPanelSlightlyVisible = visible; |
| if (visible) { |
| // tell the notification manager to turn off the lights. |
| mNotificationCallbacks.onPanelRevealed(); |
| } |
| } |
| } |
| |
| 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) { |
| performCollapse(); |
| } |
| } |
| if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { |
| if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { |
| Log.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 { |
| Log.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); |
| if (!mExpandedVisible) { |
| setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); |
| } |
| } |
| } |
| } |
| |
| private View.OnClickListener mClearButtonListener = new View.OnClickListener() { |
| public void onClick(View v) { |
| mNotificationCallbacks.onClearAll(); |
| performCollapse(); |
| } |
| }; |
| |
| private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { |
| deactivate(); |
| } |
| else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { |
| updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false), |
| intent.getStringExtra(Telephony.Intents.EXTRA_SPN), |
| intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false), |
| intent.getStringExtra(Telephony.Intents.EXTRA_PLMN)); |
| } |
| else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { |
| updateResources(); |
| } |
| } |
| }; |
| |
| void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { |
| if (false) { |
| Log.d(TAG, "updateNetworkName showSpn=" + showSpn + " spn=" + spn |
| + " showPlmn=" + showPlmn + " plmn=" + plmn); |
| } |
| boolean something = false; |
| if (showPlmn) { |
| mPlmnLabel.setVisibility(View.VISIBLE); |
| if (plmn != null) { |
| mPlmnLabel.setText(plmn); |
| } else { |
| mPlmnLabel.setText(R.string.lockscreen_carrier_default); |
| } |
| } else { |
| mPlmnLabel.setText(""); |
| mPlmnLabel.setVisibility(View.GONE); |
| } |
| if (showSpn && spn != null) { |
| mSpnLabel.setText(spn); |
| mSpnLabel.setVisibility(View.VISIBLE); |
| something = true; |
| } else { |
| mSpnLabel.setText(""); |
| mSpnLabel.setVisibility(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() { |
| mClearButton.setText(mContext.getText(R.string.status_bar_clear_all_button)); |
| mOngoingTitle.setText(mContext.getText(R.string.status_bar_ongoing_events_title)); |
| mLatestTitle.setText(mContext.getText(R.string.status_bar_latest_events_title)); |
| mNoNotificationsTitle.setText(mContext.getText(R.string.status_bar_no_notifications_title)); |
| Log.d(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); |
| Log.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(); |
| Log.d(TAG, "stopTracing"); |
| vibrate(); |
| } |
| }; |
| |
| class UninstallReceiver extends BroadcastReceiver { |
| public UninstallReceiver() { |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_PACKAGE_REMOVED); |
| filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); |
| filter.addDataScheme("package"); |
| mContext.registerReceiver(this, filter); |
| } |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| ArrayList<StatusBarNotification> list = null; |
| synchronized (StatusBarService.this) { |
| Uri data = intent.getData(); |
| if (data != null) { |
| String pkg = data.getSchemeSpecificPart(); |
| list = mNotificationData.notificationsForPackage(pkg); |
| } |
| } |
| |
| if (list != null) { |
| final int N = list.size(); |
| for (int i=0; i<N; i++) { |
| removeIcon(list.get(i).key); |
| } |
| } |
| } |
| } |
| } |