diff options
author | Joe Onorato <joeo@android.com> | 2010-05-26 17:03:26 -0400 |
---|---|---|
committer | Joe Onorato <joeo@android.com> | 2010-06-02 14:48:45 -0700 |
commit | 79de0c550037a5328bbc7f4fddaf02f192a5c283 (patch) | |
tree | 78ecd96630ea193e5ff04364f01f4fe4a2f65e63 /packages/SystemUI/src/com | |
parent | 7dd8c6e4eae18a5a0f5f834f39f58f868f1fc720 (diff) | |
download | frameworks_base-79de0c550037a5328bbc7f4fddaf02f192a5c283.zip frameworks_base-79de0c550037a5328bbc7f4fddaf02f192a5c283.tar.gz frameworks_base-79de0c550037a5328bbc7f4fddaf02f192a5c283.tar.bz2 |
Move the StatusBarPhone package into a new catch-all SystemUI.apk.
Diffstat (limited to 'packages/SystemUI/src/com')
20 files changed, 3380 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java new file mode 100644 index 0000000..70d4d6a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.RemoteViews.RemoteView; + +@RemoteView +public class AnimatedImageView extends ImageView { + AnimationDrawable mAnim; + boolean mAttached; + + public AnimatedImageView(Context context) { + super(context); + } + + public AnimatedImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private void updateAnim() { + Drawable drawable = getDrawable(); + if (mAttached && mAnim != null) { + mAnim.stop(); + } + if (drawable instanceof AnimationDrawable) { + mAnim = (AnimationDrawable)drawable; + if (mAttached) { + mAnim.start(); + } + } else { + mAnim = null; + } + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + updateAnim(); + } + + @Override + @android.view.RemotableViewMethod + public void setImageResource(int resid) { + super.setImageResource(resid); + updateAnim(); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mAnim != null) { + mAnim.start(); + } + mAttached = true; + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mAnim != null) { + mAnim.stop(); + } + mAttached = false; + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/Clock.java new file mode 100644 index 0000000..eff05dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/Clock.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.BroadcastReceiver; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.text.format.DateFormat; +import android.text.style.RelativeSizeSpan; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.TimeZone; + +import com.android.internal.R; + +/** + * This widget display an analogic clock with two hands for hours and + * minutes. + */ +public class Clock extends TextView { + private final Handler mHandler = new Handler(); + private boolean mAttached; + private Calendar mCalendar; + private String mClockFormatString; + private SimpleDateFormat mClockFormat; + + public Clock(Context context) { + this(context, null); + } + + public Clock(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public Clock(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mAttached) { + mAttached = true; + IntentFilter filter = new IntentFilter(); + + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + + getContext().registerReceiver(mIntentReceiver, filter, null, mHandler); + } + + // NOTE: It's safe to do these after registering the receiver since the receiver always runs + // in the main thread, therefore the receiver can't run before this method returns. + + // The time zone may have changed while the receiver wasn't registered, so update the Time + mCalendar = Calendar.getInstance(TimeZone.getDefault()); + + // Make sure we update to the current time + updateClock(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mAttached) { + getContext().unregisterReceiver(mIntentReceiver); + mAttached = false; + } + } + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { + String tz = intent.getStringExtra("time-zone"); + mCalendar = Calendar.getInstance(TimeZone.getTimeZone(tz)); + if (mClockFormat != null) { + mClockFormat.setTimeZone(mCalendar.getTimeZone()); + } + } + updateClock(); + } + }; + + final void updateClock() { + mCalendar.setTimeInMillis(System.currentTimeMillis()); + setText(getSmallTime()); + } + + private final CharSequence getSmallTime() { + Context context = getContext(); + boolean b24 = DateFormat.is24HourFormat(context); + int res; + + if (b24) { + res = R.string.twenty_four_hour_time_format; + } else { + res = R.string.twelve_hour_time_format; + } + + final char MAGIC1 = '\uEF00'; + final char MAGIC2 = '\uEF01'; + + SimpleDateFormat sdf; + String format = context.getString(res); + if (!format.equals(mClockFormatString)) { + /* + * Search for an unquoted "a" in the format string, so we can + * add dummy characters around it to let us find it again after + * formatting and change its size. + */ + int a = -1; + boolean quoted = false; + for (int i = 0; i < format.length(); i++) { + char c = format.charAt(i); + + if (c == '\'') { + quoted = !quoted; + } + + if (!quoted && c == 'a') { + a = i; + break; + } + } + + if (a >= 0) { + // Move a back so any whitespace before the AM/PM is also in the alternate size. + final int b = a; + while (a > 0 && Character.isWhitespace(format.charAt(a-1))) { + a--; + } + format = format.substring(0, a) + MAGIC1 + format.substring(a, b) + + "a" + MAGIC2 + format.substring(b + 1); + } + + mClockFormat = sdf = new SimpleDateFormat(format); + mClockFormatString = format; + } else { + sdf = mClockFormat; + } + String result = sdf.format(mCalendar.getTime()); + + int magic1 = result.indexOf(MAGIC1); + int magic2 = result.indexOf(MAGIC2); + + if (magic1 >= 0 && magic2 > magic1) { + SpannableStringBuilder formatted = new SpannableStringBuilder(result); + + formatted.setSpan(new RelativeSizeSpan(0.7f), magic1, magic2, + Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + + formatted.delete(magic2, magic2 + 1); + formatted.delete(magic1, magic1 + 1); + + return formatted; + } else { + return result; + } + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CloseDragHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/CloseDragHandle.java new file mode 100644 index 0000000..0f6723e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CloseDragHandle.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.LinearLayout; + + +public class CloseDragHandle extends LinearLayout { + PhoneStatusBarService mService; + + public CloseDragHandle(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Ensure that, if there is no target under us to receive the touch, + * that we process it ourself. This makes sure that onInterceptTouchEvent() + * is always called for the entire gesture. + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() != MotionEvent.ACTION_DOWN) { + mService.interceptTouchEvent(event); + } + return true; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return mService.interceptTouchEvent(event) + ? true : super.onInterceptTouchEvent(event); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java new file mode 100644 index 0000000..5a888fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.util.Slog; + +import com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarIconList; +import com.android.internal.statusbar.StatusBarNotification; +import com.android.internal.statusbar.StatusBarNotificationList; + +/** + * This class takes the functions from IStatusBar that come in on + * binder pool threads and posts messages to get them onto the main + * thread, and calls onto Callbacks. It also takes care of + * coalescing these calls so they don't stack up. For the calls + * are coalesced, note that they are all idempotent. + */ +class CommandQueue extends IStatusBar.Stub { + private static final String TAG = "StatusBar.CommandQueue"; + + private static final int MSG_MASK = 0xffff0000; + private static final int INDEX_MASK = 0x0000ffff; + + private static final int MSG_ICON = 0x00010000; + private static final int OP_SET_ICON = 1; + private static final int OP_REMOVE_ICON = 2; + + private static final int MSG_ADD_NOTIFICATION = 0x00020000; + private static final int MSG_UPDATE_NOTIFICATION = 0x00030000; + private static final int MSG_REMOVE_NOTIFICATION = 0x00040000; + + private static final int MSG_DISABLE = 0x00050000; + + private static final int MSG_SET_VISIBILITY = 0x00060000; + private static final int OP_EXPAND = 1; + private static final int OP_COLLAPSE = 2; + + private StatusBarIconList mList; + private Callbacks mCallbacks; + private Handler mHandler = new H(); + + private class NotificationQueueEntry { + IBinder key; + StatusBarNotification notification; + } + + /** + * These methods are called back on the main thread. + */ + public interface Callbacks { + public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon); + public void updateIcon(String slot, int index, int viewIndex, + StatusBarIcon old, StatusBarIcon icon); + public void removeIcon(String slot, int index, int viewIndex); + public void addNotification(IBinder key, StatusBarNotification notification); + public void updateNotification(IBinder key, StatusBarNotification notification); + public void removeNotification(IBinder key); + public void disable(int state); + public void animateExpand(); + public void animateCollapse(); + } + + public CommandQueue(Callbacks callbacks, StatusBarIconList list) { + mCallbacks = callbacks; + mList = list; + } + + public void setIcon(int index, StatusBarIcon icon) { + synchronized (mList) { + int what = MSG_ICON | index; + mHandler.removeMessages(what); + mHandler.obtainMessage(what, OP_SET_ICON, 0, icon.clone()).sendToTarget(); + } + } + + public void removeIcon(int index) { + synchronized (mList) { + int what = MSG_ICON | index; + mHandler.removeMessages(what); + mHandler.obtainMessage(what, OP_REMOVE_ICON, 0, null).sendToTarget(); + } + } + + public void addNotification(IBinder key, StatusBarNotification notification) { + synchronized (mList) { + NotificationQueueEntry ne = new NotificationQueueEntry(); + ne.key = key; + ne.notification = notification; + mHandler.obtainMessage(MSG_ADD_NOTIFICATION, 0, 0, ne).sendToTarget(); + } + } + + public void updateNotification(IBinder key, StatusBarNotification notification) { + synchronized (mList) { + NotificationQueueEntry ne = new NotificationQueueEntry(); + ne.key = key; + ne.notification = notification; + mHandler.obtainMessage(MSG_UPDATE_NOTIFICATION, 0, 0, ne).sendToTarget(); + } + } + + public void removeNotification(IBinder key) { + synchronized (mList) { + mHandler.obtainMessage(MSG_REMOVE_NOTIFICATION, 0, 0, key).sendToTarget(); + } + } + + public void disable(int state) { + synchronized (mList) { + mHandler.removeMessages(MSG_DISABLE); + mHandler.obtainMessage(MSG_DISABLE, state, 0, null).sendToTarget(); + } + } + + public void animateExpand() { + synchronized (mList) { + mHandler.removeMessages(MSG_SET_VISIBILITY); + mHandler.obtainMessage(MSG_SET_VISIBILITY, OP_EXPAND, 0, null).sendToTarget(); + } + } + + public void animateCollapse() { + synchronized (mList) { + mHandler.removeMessages(MSG_SET_VISIBILITY); + mHandler.obtainMessage(MSG_SET_VISIBILITY, OP_COLLAPSE, 0, null).sendToTarget(); + } + } + + private final class H extends Handler { + public void handleMessage(Message msg) { + final int what = msg.what & MSG_MASK; + Slog.d(TAG, "handleMessage what=0x" + Integer.toHexString(what) + " arg1=" + msg.arg1); + switch (what) { + case MSG_ICON: { + final int index = msg.what & INDEX_MASK; + final int viewIndex = mList.getViewIndex(index); + switch (msg.arg1) { + case OP_SET_ICON: { + StatusBarIcon icon = (StatusBarIcon)msg.obj; + StatusBarIcon old = mList.getIcon(index); + if (old == null) { + mList.setIcon(index, icon); + mCallbacks.addIcon(mList.getSlot(index), index, viewIndex, icon); + } else { + mList.setIcon(index, icon); + mCallbacks.updateIcon(mList.getSlot(index), index, viewIndex, + old, icon); + } + break; + } + case OP_REMOVE_ICON: + mList.removeIcon(index); + mCallbacks.removeIcon(mList.getSlot(index), index, viewIndex); + break; + } + break; + } + case MSG_ADD_NOTIFICATION: { + final NotificationQueueEntry ne = (NotificationQueueEntry)msg.obj; + mCallbacks.addNotification(ne.key, ne.notification); + break; + } + case MSG_UPDATE_NOTIFICATION: { + final NotificationQueueEntry ne = (NotificationQueueEntry)msg.obj; + mCallbacks.updateNotification(ne.key, ne.notification); + break; + } + case MSG_REMOVE_NOTIFICATION: { + mCallbacks.removeNotification((IBinder)msg.obj); + break; + } + case MSG_DISABLE: + mCallbacks.disable(msg.arg1); + break; + case MSG_SET_VISIBILITY: + if (msg.arg1 == OP_EXPAND) { + mCallbacks.animateExpand(); + } else { + mCallbacks.animateCollapse(); + } + } + } + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DateView.java new file mode 100644 index 0000000..e6d3a7e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DateView.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.AttributeSet; +import android.util.Slog; +import android.widget.TextView; +import android.view.MotionEvent; + +import java.text.DateFormat; +import java.util.Date; + +public final class DateView extends TextView { + private static final String TAG = "DateView"; + + private boolean mUpdating = false; + + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_TIME_TICK) + || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { + updateClock(); + } + } + }; + + public DateView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + setUpdates(false); + } + + @Override + protected int getSuggestedMinimumWidth() { + // makes the large background bitmap not force us to full width + return 0; + } + + private final void updateClock() { + Date now = new Date(); + setText(DateFormat.getDateInstance(DateFormat.LONG).format(now)); + } + + void setUpdates(boolean update) { + if (update != mUpdating) { + mUpdating = update; + if (update) { + // Register for Intent broadcasts for the clock and battery + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + mContext.registerReceiver(mIntentReceiver, filter, null, null); + updateClock(); + } else { + mContext.unregisterReceiver(mIntentReceiver); + } + } + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandedView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandedView.java new file mode 100644 index 0000000..c5b901f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandedView.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.Display; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.util.Slog; + + +public class ExpandedView extends LinearLayout { + PhoneStatusBarService mService; + int mPrevHeight = -1; + + public ExpandedView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + } + + /** We want to shrink down to 0, and ignore the background. */ + @Override + public int getSuggestedMinimumHeight() { + return 0; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + int height = bottom - top; + if (height != mPrevHeight) { + //Slog.d(PhoneStatusBarService.TAG, "height changed old=" + mPrevHeight + // + " new=" + height); + mPrevHeight = height; + mService.updateExpandedViewPos(PhoneStatusBarService.EXPANDED_LEAVE_ALONE); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FixedSizeDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/FixedSizeDrawable.java new file mode 100644 index 0000000..eb22b61 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FixedSizeDrawable.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.graphics.drawable.Drawable; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Rect; +import android.util.Slog; + +class FixedSizeDrawable extends Drawable { + Drawable mDrawable; + int mLeft; + int mTop; + int mRight; + int mBottom; + + FixedSizeDrawable(Drawable that) { + mDrawable = that; + } + + public void setFixedBounds(int l, int t, int r, int b) { + mLeft = l; + mTop = t; + mRight = r; + mBottom = b; + } + + public void setBounds(Rect bounds) { + mDrawable.setBounds(mLeft, mTop, mRight, mBottom); + } + + public void setBounds(int l, int t, int r, int b) { + mDrawable.setBounds(mLeft, mTop, mRight, mBottom); + } + + public void draw(Canvas canvas) { + mDrawable.draw(canvas); + } + + public int getOpacity() { + return mDrawable.getOpacity(); + } + + public void setAlpha(int alpha) { + mDrawable.setAlpha(alpha); + } + + public void setColorFilter(ColorFilter cf) { + mDrawable.setColorFilter(cf); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java new file mode 100644 index 0000000..26f5f51 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + +public class IconMerger extends LinearLayout { + PhoneStatusBarService service; + + public IconMerger(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (true) { + return; + } + + final int maxWidth = r - l; + final int N = getChildCount(); + int i; + + // get the rightmost one, and see if we even need to do anything + int fitRight = -1; + for (i=N-1; i>=0; i--) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + fitRight = child.getRight(); + break; + } + } + + // find the first visible one that isn't the more icon + View moreView = null; + int fitLeft = -1; + int startIndex = -1; + for (i=0; i<N; i++) { + final View child = getChildAt(i); + if (com.android.internal.R.drawable.stat_notify_more == child.getId()) { + moreView = child; + startIndex = i+1; + } + else if (child.getVisibility() != GONE) { + fitLeft = child.getLeft(); + break; + } + } + + if (moreView == null || startIndex < 0) { + throw new RuntimeException("Status Bar / IconMerger moreView == null"); + } + + // if it fits without the more icon, then hide the more icon and update fitLeft + // so everything gets pushed left + int adjust = 0; + if (fitRight - fitLeft <= maxWidth) { + adjust = fitLeft - moreView.getLeft(); + fitLeft -= adjust; + fitRight -= adjust; + moreView.layout(0, moreView.getTop(), 0, moreView.getBottom()); + } + int extra = fitRight - r; + int shift = -1; + + int breakingPoint = fitLeft + extra + adjust; + int number = 0; + for (i=startIndex; i<N; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + int childLeft = child.getLeft(); + int childRight = child.getRight(); + if (childLeft < breakingPoint) { + // hide this one + child.layout(0, child.getTop(), 0, child.getBottom()); + int n = 0; // XXX this.service.getIconNumberForView(child); + if (n == 0) { + number += 1; + } else if (n > 0) { + number += n; + } + } else { + // decide how much to shift by + if (shift < 0) { + shift = childLeft - fitLeft; + } + // shift this left by shift + child.layout(childLeft-shift, child.getTop(), + childRight-shift, child.getBottom()); + } + } + } + + // BUG: Updating the text during the layout here doesn't seem to cause + // the view to be redrawn fully. The text view gets resized correctly, but the + // text contents aren't drawn properly. To work around this, we post a message + // and provide the value later. We're the only one changing this value show it + // should be ordered correctly. + if (false) { + // TODO this.moreIcon.update(number); + } else { + mBugWorkaroundNumber = number; + mBugWorkaroundHandler.post(mBugWorkaroundRunnable); + } + } + + private int mBugWorkaroundNumber; + private Handler mBugWorkaroundHandler = new Handler(); + private Runnable mBugWorkaroundRunnable = new Runnable() { + public void run() { + /* TODO + IconMerger.this.moreIcon.update(mBugWorkaroundNumber); + IconMerger.this.moreIcon.view.invalidate(); + */ + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java new file mode 100644 index 0000000..1e89624 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Slog; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +public class LatestItemView extends FrameLayout { + + public LatestItemView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public boolean dispatchTouchEvent(MotionEvent ev) { + return onTouchEvent(ev); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java new file mode 100644 index 0000000..7a82267 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.app.Notification; +import android.os.IBinder; +import android.view.View; + +import com.android.internal.statusbar.StatusBarNotification; + +import java.util.ArrayList; + +/** + * The list of currently displaying notifications. + */ +public class NotificationData { + public static final class Entry { + public IBinder key; + public StatusBarNotification notification; + public StatusBarIconView icon; + public View row; // the outer expanded view + public View content; // takes the click events and sends the PendingIntent + public View expanded; // the inflated RemoteViews + } + private final ArrayList<Entry> mEntries = new ArrayList<Entry>(); + + public int size() { + return mEntries.size(); + } + + public Entry getEntryAt(int index) { + return mEntries.get(index); + } + + public int findEntry(IBinder key) { + final int N = mEntries.size(); + for (int i=0; i<N; i++) { + Entry entry = mEntries.get(i); + if (entry.key == key) { + return i; + } + } + return -1; + } + + public int add(IBinder key, StatusBarNotification notification, View row, View content, + View expanded, StatusBarIconView icon) { + Entry entry = new Entry(); + entry.key = key; + entry.notification = notification; + entry.row = row; + entry.content = content; + entry.expanded = expanded; + entry.icon = icon; + final int index = chooseIndex(notification.notification.when); + mEntries.add(index, entry); + return index; + } + + public Entry remove(IBinder key) { + final int N = mEntries.size(); + for (int i=0; i<N; i++) { + Entry entry = mEntries.get(i); + if (entry.key == key) { + mEntries.remove(i); + return entry; + } + } + return null; + } + + private int chooseIndex(final long when) { + final int N = mEntries.size(); + for (int i=0; i<N; i++) { + Entry entry = mEntries.get(i); + if (entry.notification.notification.when > when) { + return i; + } + } + return N; + } + + /** + * Return whether there are any visible items (i.e. items without an error). + */ + public boolean hasVisibleItems() { + final int N = mEntries.size(); + for (int i=0; i<N; i++) { + Entry entry = mEntries.get(i); + if (entry.expanded != null) { // the view successfully inflated + return true; + } + } + return false; + } + + /** + * Return whether there are any clearable items (that aren't errors). + */ + public boolean hasClearableItems() { + final int N = mEntries.size(); + for (int i=0; i<N; i++) { + Entry entry = mEntries.get(i); + if (entry.expanded != null) { // the view successfully inflated + if ((entry.notification.notification.flags & Notification.FLAG_NO_CLEAR) == 0) { + return true; + } + } + } + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLinearLayout.java new file mode 100644 index 0000000..8105352 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLinearLayout.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + + +public class NotificationLinearLayout extends LinearLayout { + public NotificationLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java new file mode 100644 index 0000000..925095e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java @@ -0,0 +1,1437 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import com.android.internal.util.CharSequences; +import com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarNotification; + +import android.app.ActivityManagerNative; +import android.app.Dialog; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +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.Rect; +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.Slog; +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; + +import com.android.systemui.R; + +public class PhoneStatusBarService extends StatusBarService { + static final String TAG = "PhoneStatusBarService"; + static final boolean SPEW = false; + + 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; + + public interface NotificationCallbacks { + void onSetDisabled(int status); + void onClearAll(); + void onNotificationClick(String pkg, String tag, 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) { + animateCollapse(); + } + return true; + } + return super.dispatchKeyEvent(event); + } + } + + int mHeight; + int mIconWidth; + + Display mDisplay; + StatusBarView mStatusBarView; + int mPixelFormat; + H mHandler = new H(); + Object mQueueLock = new Object(); + + // icons + String[] mRightIconSlots; + LinearLayout mIcons; + IconMerger mNotificationIcons; + LinearLayout mStatusIcons; + + // expanded notifications + Dialog mExpandedDialog; + ExpandedView mExpandedView; + WindowManager.LayoutParams mExpandedParams; + ScrollView mScrollView; + View mNotificationLinearLayout; + View mExpandedContents; + // top bar + TextView mNoNotificationsTitle; + TextView mSpnLabel; + TextView mPlmnLabel; + TextView mClearButton; + // drag bar + CloseDragHandle mCloseView; + // ongoing + NotificationData mOngoing = new NotificationData(); + TextView mOngoingTitle; + LinearLayout mOngoingItems; + // latest + NotificationData mLatest = new NotificationData(); + TextView mLatestTitle; + LinearLayout mLatestItems; + // position + int[] mPositionTmp = new int[2]; + boolean mExpanded; + boolean mExpandedVisible; + + // the date view + DateView mDateView; + + // the tracker view + TrackingView mTrackingView; + WindowManager.LayoutParams mTrackingParams; + int mTrackingPosition; // the position of the top of the tracking view. + + // ticker + private Ticker mTicker; + private View mTickerView; + private boolean mTicking; + + // 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 mDisplayHeight; + 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; + + /** + * Construct the service, add the status bar view to the window manager + */ + @Override + public void onCreate() { + // First set up our views and stuff. + mDisplay = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + makeStatusBarView(this); + + // Next, call super.onCreate(), which will populate our views. + super.onCreate(); + } + + // ================================================================================ + // Constructing the view + // ================================================================================ + private void makeStatusBarView(Context context) { + Resources res = context.getResources(); + mRightIconSlots = res.getStringArray(R.array.status_bar_icon_order); + + mHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + mIconWidth = mHeight; + + ExpandedView expanded = (ExpandedView)View.inflate(context, + R.layout.status_bar_expanded, null); + expanded.mService = this; + StatusBarView sb = (StatusBarView)View.inflate(context, 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; + mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout); + 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, 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(); + mDateView.setVisibility(View.INVISIBLE); + + // before we register for broadcasts + mPlmnLabel.setText(com.android.internal.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(Intent.ACTION_SCREEN_OFF); + filter.addAction(Telephony.Intents.SPN_STRINGS_UPDATED_ACTION); + context.registerReceiver(mBroadcastReceiver, filter); + } + + @Override + protected void addStatusBarView() { + final StatusBarView view = mStatusBarView; + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + mHeight, + WindowManager.LayoutParams.TYPE_STATUS_BAR, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING, + PixelFormat.RGBX_8888); + lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; + lp.setTitle("StatusBar"); + // TODO lp.windowAnimations = R.style.Animation_StatusBar; + + WindowManagerImpl.getDefault().addView(view, lp); + } + + public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { + Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex + + " icon=" + icon); + StatusBarIconView view = new StatusBarIconView(this, slot); + view.set(icon); + mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconWidth, mHeight)); + } + + public void updateIcon(String slot, int index, int viewIndex, + StatusBarIcon old, StatusBarIcon icon) { + 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) { + Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex); + mStatusIcons.removeViewAt(viewIndex); + } + + public void addNotification(IBinder key, StatusBarNotification notification) { + addNotificationViews(key, notification); + + // show the ticker + // TODO + + // Recalculate the position of the sliding windows and the titles. + setAreThereNotifications(); + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); + } + + public void updateNotification(IBinder key, StatusBarNotification notification) { + Slog.d(TAG, "updateNotification key=" + key + " notification=" + notification); + + NotificationData oldList; + int oldIndex = mOngoing.findEntry(key); + if (oldIndex >= 0) { + oldList = mOngoing; + } else { + oldIndex = mLatest.findEntry(key); + if (oldIndex < 0) { + Slog.w(TAG, "updateNotification for unknown key: " + key); + return; + } + oldList = mLatest; + } + final NotificationData.Entry oldEntry = oldList.getEntryAt(oldIndex); + final StatusBarNotification oldNotification = oldEntry.notification; + final RemoteViews oldContentView = oldNotification.notification.contentView; + + final RemoteViews contentView = notification.notification.contentView; + + // Can we just reapply the RemoteViews in place? If when didn't change, the order + // didn't change. + if (notification.notification.when == oldNotification.notification.when + && notification.isOngoing() == oldNotification.isOngoing() + && oldEntry.expanded != null + && contentView != null && oldContentView != null + && contentView.getPackage() != null + && oldContentView.getPackage() != null + && oldContentView.getPackage().equals(contentView.getPackage()) + && oldContentView.getLayoutId() == contentView.getLayoutId()) { + Slog.d(TAG, "reusing notification"); + oldEntry.notification = notification; + try { + // Reapply the RemoteViews + contentView.reapply(this, oldEntry.content); + // update the contentIntent + final PendingIntent contentIntent = notification.notification.contentIntent; + if (contentIntent != null) { + oldEntry.content.setOnClickListener(new Launcher(contentIntent, + notification.pkg, notification.tag, notification.id)); + } + } + 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); + } + // Update the icon. + oldEntry.icon.set(new StatusBarIcon(notification.pkg, notification.notification.icon, + notification.notification.iconLevel, notification.notification.number)); + } else { + Slog.d(TAG, "not reusing notification"); + removeNotificationViews(key); + addNotificationViews(key, notification); + } + + // Restart the ticker if it's still running + // TODO + + // Recalculate the position of the sliding windows and the titles. + setAreThereNotifications(); + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); + } + + public void removeNotification(IBinder key) { + Slog.d(TAG, "removeNotification key=" + key); + removeNotificationViews(key); + + // Cancel the ticker if it's still running + // TODO + + // Recalculate the position of the sliding windows and the titles. + setAreThereNotifications(); + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); + } + + private int chooseIconIndex(boolean isOngoing, int index) { + final int ongoingSize = mOngoing.size(); + final int latestSize = mLatest.size(); + if (!isOngoing) { + index = mLatest.size() + index; + } + return (ongoingSize + latestSize) - index - 1; + } + + 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)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, notification.pkg, + notification.tag, notification.id)); + } + + View expanded = null; + Exception exception = null; + try { + expanded = remoteViews.apply(this, content); + } + catch (RuntimeException e) { + exception = e; + } + if (expanded == null) { + Slog.e(TAG, "couldn't inflate view for package " + notification.pkg, exception); + row.setVisibility(View.GONE); + } else { + content.addView(expanded); + row.setDrawingCacheEnabled(true); + } + + return new View[] { row, content, expanded }; + } + + void addNotificationViews(IBinder key, StatusBarNotification notification) { + NotificationData list; + ViewGroup parent; + final boolean isOngoing = notification.isOngoing(); + if (isOngoing) { + list = mOngoing; + parent = mOngoingItems; + } else { + list = mLatest; + parent = mLatestItems; + } + // Construct the expanded view. + final View[] views = makeNotificationView(notification, parent); + final View row = views[0]; + final View content = views[1]; + final View expanded = views[2]; + // Construct the icon. + StatusBarIconView iconView = new StatusBarIconView(this, + notification.pkg + "/" + notification.id); + iconView.set(new StatusBarIcon(notification.pkg, notification.notification.icon, + notification.notification.iconLevel, notification.notification.number)); + // Add the expanded view. + final int viewIndex = list.add(key, notification, row, content, expanded, iconView); + parent.addView(row, viewIndex); + // Add the icon. + final int iconIndex = chooseIconIndex(isOngoing, viewIndex); + mNotificationIcons.addView(iconView, iconIndex, + new LinearLayout.LayoutParams(mIconWidth, mHeight)); + } + + void removeNotificationViews(IBinder key) { + NotificationData.Entry entry = mOngoing.remove(key); + if (entry == null) { + entry = mLatest.remove(key); + if (entry == null) { + Slog.w(TAG, "removeNotification for unknown key: " + key); + return; + } + } + // Remove the expanded view. + ((ViewGroup)entry.row.getParent()).removeView(entry.row); + // Remove the icon. + ((ViewGroup)entry.icon.getParent()).removeView(entry.icon); + } + + private void setAreThereNotifications() { + boolean ongoing = mOngoing.hasVisibleItems(); + boolean latest = mLatest.hasVisibleItems(); + + // (no ongoing notifications are clearable) + if (mLatest.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); + } + } + + + /** + * 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 ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { + if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { + Slog.d(TAG, "DISABLE_EXPAND: yes"); + animateCollapse(); + } + } + if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); + if (mTicking) { + 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 && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { + Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: yes"); + 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; + } + } + } + + 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; + mExpandedDialog.getWindow().setAttributes(mExpandedParams); + mExpandedView.requestFocus(View.FOCUS_FORWARD); + mTrackingView.setVisibility(View.VISIBLE); + + if (!mTicking) { + setDateViewVisibility(true, com.android.internal.R.anim.fade_in); + } + } + + 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, 2000.0f, true); + } + + public void animateCollapse() { + if (SPEW) { + Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded + + " mExpandedVisible=" + mExpandedVisible + + " mExpanded=" + mExpanded + + " mAnimating=" + mAnimating + + " mAnimY=" + mAnimY + + " mAnimVel=" + mAnimVel); + } + + if (!mExpandedVisible) { + return; + } + + int y; + if (mAnimating) { + y = (int)mAnimY; + } else { + y = mDisplay.getHeight()-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, -2000.0f, 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); + } + setDateViewVisibility(false, com.android.internal.R.anim.fade_out); + + 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 >= mDisplay.getHeight()-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) { + mTracking = true; + mVelocityTracker = VelocityTracker.obtain(); + if (opening) { + 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); + 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) { + mAnimatingReveal = false; + mDisplayHeight = mDisplay.getHeight(); + + mAnimY = y; + mAnimVel = vel; + + //Slog.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; + } + } + } + //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); + } + + if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { + return false; + } + + final int statusBarSize = mStatusBarView.getHeight(); + final int hitSize = statusBarSize*2; + if (event.getAction() == MotionEvent.ACTION_DOWN) { + final 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))) { + + // 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 < mDisplay.getWidth() - edgeBorder) { + prepareTracking(y, !mExpanded);// opening if we're not already fully visible + 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 String mTag; + private int mId; + + Launcher(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(); + } catch (RemoteException e) { + } + 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(PhoneStatusBarService.this, 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); + } + try { + mBarService.onNotificationClick(mPkg, mTag, mId); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + animateCollapse(); + } + } + + 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(PhoneStatusBarService.this, 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 (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 (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(" mDisplayHeight=" + mDisplayHeight + + " mAnimatingReveal=" + mAnimatingReveal + + " mViewDelta=" + mViewDelta); + pw.println(" mDisplayHeight=" + mDisplayHeight); + 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 (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); + } + } + */ + + 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(); + } + }); + } + } + + void onBarViewAttached() { + WindowManager.LayoutParams lp; + int pixelFormat; + Drawable bg; + + /// ---------- Tracking View -------------- + pixelFormat = PixelFormat.RGBX_8888; + 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_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"); + lp.y = mTrackingPosition; + mTrackingParams = lp; + + WindowManagerImpl.getDefault().addView(mTrackingView, lp); + } + + void onTrackingViewAttached() { + WindowManager.LayoutParams lp; + int pixelFormat; + Drawable bg; + + /// ---------- Expanded View -------------- + pixelFormat = PixelFormat.TRANSLUCENT; + + final int disph = mDisplay.getHeight(); + lp = mExpandedDialog.getWindow().getAttributes(); + lp.width = ViewGroup.LayoutParams.MATCH_PARENT; + lp.height = getExpandedHeight(); + lp.x = 0; + mTrackingPosition = lp.y = -disph; // sufficiently large negative + 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_DITHER + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + 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.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + mExpandedDialog.getWindow().setBackgroundDrawable(null); + mExpandedDialog.show(); + FrameLayout hack = (FrameLayout)mExpandedView.getParent(); + } + + 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) { + Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition + + " mTrackingParams.y=" + mTrackingParams.y + + " mTrackingPosition=" + mTrackingPosition); + } + + int h = mStatusBarView.getHeight(); + int disph = mDisplay.getHeight(); + + // If the expanded view is not visible, make sure they're still off screen. + // Maybe the view was resized. + if (!mExpandedVisible) { + if (mTrackingView != null) { + mTrackingPosition = -disph; + if (mTrackingParams != null) { + mTrackingParams.y = mTrackingPosition; + WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); + } + } + if (mExpandedParams != null) { + mExpandedParams.y = -disph; + mExpandedDialog.getWindow().setAttributes(mExpandedParams); + } + 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) { + 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; + 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; + } + visibilityChanged(visible); + mExpandedDialog.getWindow().setAttributes(mExpandedParams); + } + + 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() { + return mDisplay.getHeight() - mStatusBarView.getHeight() - mCloseView.getHeight(); + } + + void updateExpandedHeight() { + if (mExpandedView != null) { + mExpandedParams.height = getExpandedHeight(); + mExpandedDialog.getWindow().setAttributes(mExpandedParams); + } + } + + /** + * 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 visibilityChanged(boolean visible) { + if (mPanelSlightlyVisible != visible) { + mPanelSlightlyVisible = visible; + try { + mBarService.visibilityChanged(visible); + } 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 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)) { + //collapse(); + } + 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) { + Slog.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(com.android.internal.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() { + Resources res = getResources(); + + mClearButton.setText(getText(R.string.status_bar_clear_all_button)); + mOngoingTitle.setText(getText(R.string.status_bar_ongoing_events_title)); + mLatestTitle.setText(getText(R.string.status_bar_latest_events_title)); + mNoNotificationsTitle.setText(getText(R.string.status_bar_no_notifications_title)); + + mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); + + if (false) Slog.v(TAG, "updateResources"); + } + + // + // tracing + // + + void postStartTracing() { + mHandler.postDelayed(mStartTracing, 3000); + } + + void vibrate() { + android.os.Vibrator vib = (android.os.Vibrator)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(); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java new file mode 100644 index 0000000..959ac7c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.graphics.Canvas; +import android.util.Slog; +import android.view.ViewDebug; +import android.widget.FrameLayout; + +import com.android.internal.statusbar.StatusBarIcon; + +public class StatusBarIconView extends AnimatedImageView { + private static final String TAG = "StatusBarIconView"; + + private StatusBarIcon mIcon; + @ViewDebug.ExportedProperty private String mSlot; + @ViewDebug.ExportedProperty private boolean mError; + + public StatusBarIconView(Context context, String slot) { + super(context); + mSlot = slot; + } + + private static boolean streq(String a, String b) { + if (a == b) { + return true; + } + if (a == null && b != null) { + return false; + } + if (a != null && b == null) { + return false; + } + return a.equals(b); + } + + public void set(StatusBarIcon icon) { + error: { + final boolean iconEquals = !mError + && mIcon != null + && streq(mIcon.iconPackage, icon.iconPackage) + && mIcon.iconId == icon.iconId; + final boolean levelEquals = !mError + && iconEquals + && mIcon.iconLevel == icon.iconLevel; + final boolean visibilityEquals = !mError + && mIcon != null + && mIcon.visible == icon.visible; + mError = false; + if (!iconEquals) { + Drawable drawable = getIcon(icon); + if (drawable == null) { + mError = true; + break error; + } + setImageDrawable(drawable); + } + if (!levelEquals) { + setImageLevel(icon.iconLevel); + } + if (!visibilityEquals) { + setVisibility(icon.visible ? VISIBLE : GONE); + } + mIcon = icon.clone(); + } + if (mError) { + setVisibility(GONE); + } + } + + /** + * Returns the right icon to use for this item, respecting the iconId and + * iconPackage (if set) + * + * @param context Context to use to get resources if iconPackage is not set + * @return Drawable for this item, or null if the package or item could not + * be found + */ + private Drawable getIcon(StatusBarIcon icon) { + Context context = getContext(); + Resources r = null; + + if (icon.iconPackage != null) { + try { + r = context.getPackageManager().getResourcesForApplication(icon.iconPackage); + } catch (PackageManager.NameNotFoundException ex) { + Slog.e(PhoneStatusBarService.TAG, "Icon package not found: "+icon.iconPackage, ex); + return null; + } + } else { + r = context.getResources(); + } + + if (icon.iconId == 0) { + Slog.w(PhoneStatusBarService.TAG, "No icon ID for slot " + mSlot); + return null; + } + + try { + return r.getDrawable(icon.iconId); + } catch (RuntimeException e) { + Slog.w(PhoneStatusBarService.TAG, "Icon not found in " + + (icon.iconPackage != null ? icon.iconId : "<system>") + + ": " + Integer.toHexString(icon.iconId)); + } + + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java new file mode 100644 index 0000000..a60a40b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.Slog; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManagerImpl; + +import com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarIconList; + +public abstract class StatusBarService extends Service implements CommandQueue.Callbacks { + private static final String TAG = "StatusBarService"; + + CommandQueue mCommandQueue; + IStatusBarService mBarService; + + /* TODO + H mHandler = new H(); + Object mQueueLock = new Object(); + NotificationCallbacks mNotificationCallbacks; + */ + + @Override + public void onCreate() { + // Connect in to the status bar manager service + StatusBarIconList iconList = new StatusBarIconList(); + mCommandQueue = new CommandQueue(this, iconList); + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + try { + mBarService.registerStatusBar(mCommandQueue, iconList); + } catch (RemoteException ex) { + // If the system process isn't there we're doomed anyway. + } + + // Set up the initial icon state + final int N = iconList.size(); + int viewIndex = 0; + for (int i=0; i<N; i++) { + StatusBarIcon icon = iconList.getIcon(i); + if (icon != null) { + addIcon(iconList.getSlot(i), i, viewIndex, icon); + viewIndex++; + } + } + + // Put up the view + addStatusBarView(); + } + + @Override + public void onDestroy() { + // we're never destroyed + } + + /** + * Nobody binds to us. + */ + @Override + public IBinder onBind(Intent intent) { + return null; + } + + /** + * Implement this to add the main status bar view. + */ + protected abstract void addStatusBarView(); +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStarter.java new file mode 100644 index 0000000..2b9dfb0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStarter.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.util.Log; + +/** + * Receive a broadcast from the StatusBarManagerService at boot time, and + * kick off the StatusBarService. + */ +public class StatusBarStarter extends BroadcastReceiver { + private static final String TAG = "StatusBarStarter"; + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "StatusBarStarter onReceive intent=" + intent); + context.startService(new Intent(context, PhoneStatusBarService.class)); + } +} + + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarView.java new file mode 100644 index 0000000..466cc75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarView.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.FrameLayout; + +import com.android.systemui.R; + +public class StatusBarView extends FrameLayout { + private static final String TAG = "StatusBarView"; + + static final int DIM_ANIM_TIME = 400; + + PhoneStatusBarService mService; + boolean mTracking; + int mStartX, mStartY; + ViewGroup mNotificationIcons; + ViewGroup mStatusIcons; + View mDate; + FixedSizeDrawable mBackground; + + boolean mNightMode = false; + int mStartAlpha = 0, mEndAlpha = 0; + long mEndTime = 0; + + public StatusBarView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mNotificationIcons = (ViewGroup)findViewById(R.id.notificationIcons); + mStatusIcons = (ViewGroup)findViewById(R.id.statusIcons); + mDate = findViewById(R.id.date); + + mBackground = new FixedSizeDrawable(mDate.getBackground()); + mBackground.setFixedBounds(0, 0, 0, 0); + mDate.setBackgroundDrawable(mBackground); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mService.onBarViewAttached(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES; + if (mNightMode != nightMode) { + mNightMode = nightMode; + mStartAlpha = getCurAlpha(); + mEndAlpha = mNightMode ? 0x80 : 0x00; + mEndTime = SystemClock.uptimeMillis() + DIM_ANIM_TIME; + invalidate(); + } + } + + int getCurAlpha() { + long time = SystemClock.uptimeMillis(); + if (time > mEndTime) { + return mEndAlpha; + } + return mEndAlpha + - (int)(((mEndAlpha-mStartAlpha) * (mEndTime-time) / DIM_ANIM_TIME)); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mService.updateExpandedViewPos(PhoneStatusBarService.EXPANDED_LEAVE_ALONE); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + // put the date date view quantized to the icons + int oldDateRight = mDate.getRight(); + int newDateRight; + + newDateRight = getDateSize(mNotificationIcons, oldDateRight, + getViewOffset(mNotificationIcons)); + if (newDateRight < 0) { + int offset = getViewOffset(mStatusIcons); + if (oldDateRight < offset) { + newDateRight = oldDateRight; + } else { + newDateRight = getDateSize(mStatusIcons, oldDateRight, offset); + if (newDateRight < 0) { + newDateRight = r; + } + } + } + int max = r - getPaddingRight(); + if (newDateRight > max) { + newDateRight = max; + } + + mDate.layout(mDate.getLeft(), mDate.getTop(), newDateRight, mDate.getBottom()); + mBackground.setFixedBounds(-mDate.getLeft(), -mDate.getTop(), (r-l), (b-t)); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + int alpha = getCurAlpha(); + if (alpha != 0) { + canvas.drawARGB(alpha, 0, 0, 0); + } + if (alpha != mEndAlpha) { + invalidate(); + } + } + + /** + * Gets the left position of v in this view. Throws if v is not + * a child of this. + */ + private int getViewOffset(View v) { + int offset = 0; + while (v != this) { + offset += v.getLeft(); + ViewParent p = v.getParent(); + if (v instanceof View) { + v = (View)p; + } else { + throw new RuntimeException(v + " is not a child of " + this); + } + } + return offset; + } + + private int getDateSize(ViewGroup g, int w, int offset) { + final int N = g.getChildCount(); + for (int i=0; i<N; i++) { + View v = g.getChildAt(i); + int l = v.getLeft() + offset; + int r = v.getRight() + offset; + if (w >= l && w <= r) { + return r; + } + } + return -1; + } + + /** + * Ensure that, if there is no target under us to receive the touch, + * that we process it ourself. This makes sure that onInterceptTouchEvent() + * is always called for the entire gesture. + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() != MotionEvent.ACTION_DOWN) { + mService.interceptTouchEvent(event); + } + return true; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return mService.interceptTouchEvent(event) + ? true : super.onInterceptTouchEvent(event); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java new file mode 100644 index 0000000..c3647cb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.text.StaticLayout; +import android.text.Layout.Alignment; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.Slog; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.TextSwitcher; +import android.widget.TextView; +import android.widget.ImageSwitcher; + +import java.util.ArrayList; + +import com.android.internal.statusbar.StatusBarNotification; +import com.android.systemui.R; + +public abstract class Ticker { + private static final int TICKER_SEGMENT_DELAY = 3000; + + private final class Segment { + StatusBarNotification notificationData; + Drawable icon; + CharSequence text; + int current; + int next; + boolean first; + + StaticLayout getLayout(CharSequence substr) { + int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft() + - mTextSwitcher.getPaddingRight(); + return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true); + } + + CharSequence rtrim(CharSequence substr, int start, int end) { + while (end > start && !TextUtils.isGraphic(substr.charAt(end-1))) { + end--; + } + if (end > start) { + return substr.subSequence(start, end); + } + return null; + } + + /** returns null if there is no more text */ + CharSequence getText() { + if (this.current > this.text.length()) { + return null; + } + CharSequence substr = this.text.subSequence(this.current, this.text.length()); + StaticLayout l = getLayout(substr); + int lineCount = l.getLineCount(); + if (lineCount > 0) { + int start = l.getLineStart(0); + int end = l.getLineEnd(0); + this.next = this.current + end; + return rtrim(substr, start, end); + } else { + throw new RuntimeException("lineCount=" + lineCount + " current=" + current + + " text=" + text); + } + } + + /** returns null if there is no more text */ + CharSequence advance() { + this.first = false; + int index = this.next; + final int len = this.text.length(); + while (index < len && !TextUtils.isGraphic(this.text.charAt(index))) { + index++; + } + if (index >= len) { + return null; + } + + CharSequence substr = this.text.subSequence(index, this.text.length()); + StaticLayout l = getLayout(substr); + final int lineCount = l.getLineCount(); + int i; + for (i=0; i<lineCount; i++) { + int start = l.getLineStart(i); + int end = l.getLineEnd(i); + if (i == lineCount-1) { + this.next = len; + } else { + this.next = index + l.getLineStart(i+1); + } + CharSequence result = rtrim(substr, start, end); + if (result != null) { + this.current = index + start; + return result; + } + } + this.current = len; + return null; + } + + Segment(StatusBarNotification n, Drawable icon, CharSequence text) { + this.notificationData = n; + this.icon = icon; + this.text = text; + int index = 0; + final int len = text.length(); + while (index < len && !TextUtils.isGraphic(text.charAt(index))) { + index++; + } + this.current = index; + this.next = index; + this.first = true; + } + }; + + private Handler mHandler = new Handler(); + private ArrayList<Segment> mSegments = new ArrayList(); + private TextPaint mPaint; + private View mTickerView; + private ImageSwitcher mIconSwitcher; + private TextSwitcher mTextSwitcher; + + Ticker(Context context, StatusBarView sb) { + mTickerView = sb.findViewById(R.id.ticker); + + mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon); + mIconSwitcher.setInAnimation( + AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); + mIconSwitcher.setOutAnimation( + AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); + + mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText); + mTextSwitcher.setInAnimation( + AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); + mTextSwitcher.setOutAnimation( + AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); + + // Copy the paint style of one of the TextSwitchers children to use later for measuring + TextView text = (TextView)mTextSwitcher.getChildAt(0); + mPaint = text.getPaint(); + } + + void addEntry(StatusBarNotification n, Drawable icon, CharSequence text) { + int initialCount = mSegments.size(); + + Segment newSegment = new Segment(n, icon, text); + + // prune out any preexisting ones for this notification, but not the current one. + // let that finish, even if it's the same id + for (int i=1; i<initialCount; i++) { + Segment seg = mSegments.get(i); + if (n.id == seg.notificationData.id && n.pkg.equals(seg.notificationData.pkg)) { + // just update that one to use this new data instead + mSegments.set(i, newSegment); + // and since we know initialCount != 0, just return + return ; + } + } + + mSegments.add(newSegment); + + if (initialCount == 0 && mSegments.size() > 0) { + Segment seg = mSegments.get(0); + seg.first = false; + + mIconSwitcher.setAnimateFirstView(false); + mIconSwitcher.reset(); + mIconSwitcher.setImageDrawable(seg.icon); + + mTextSwitcher.setAnimateFirstView(false); + mTextSwitcher.reset(); + mTextSwitcher.setText(seg.getText()); + + tickerStarting(); + scheduleAdvance(); + } + } + + void halt() { + mHandler.removeCallbacks(mAdvanceTicker); + mSegments.clear(); + tickerHalting(); + } + + void reflowText() { + if (mSegments.size() > 0) { + Segment seg = mSegments.get(0); + CharSequence text = seg.getText(); + mTextSwitcher.setCurrentText(text); + } + } + + private Runnable mAdvanceTicker = new Runnable() { + public void run() { + while (mSegments.size() > 0) { + Segment seg = mSegments.get(0); + + if (seg.first) { + // this makes the icon slide in for the first one for a given + // notification even if there are two notifications with the + // same icon in a row + mIconSwitcher.setImageDrawable(seg.icon); + } + CharSequence text = seg.advance(); + if (text == null) { + mSegments.remove(0); + continue; + } + mTextSwitcher.setText(text); + + scheduleAdvance(); + break; + } + if (mSegments.size() == 0) { + tickerDone(); + } + } + }; + + private void scheduleAdvance() { + mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY); + } + + abstract void tickerStarting(); + abstract void tickerDone(); + abstract void tickerHalting(); +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java new file mode 100644 index 0000000..9749ae4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextSwitcher; + + +public class TickerView extends TextSwitcher +{ + Ticker mTicker; + + public TickerView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mTicker.reflowText(); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TrackingPatternView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TrackingPatternView.java new file mode 100644 index 0000000..ba6f15d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/TrackingPatternView.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.Slog; +import android.view.View; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap; +import android.graphics.Paint; +import android.graphics.Canvas; + +public class TrackingPatternView extends View { + private Bitmap mTexture; + private Paint mPaint; + private int mTextureWidth; + private int mTextureHeight; + + public TrackingPatternView(Context context, AttributeSet attrs) { + super(context, attrs); + + mTexture = BitmapFactory.decodeResource(getResources(), + com.android.internal.R.drawable.status_bar_background); + mTextureWidth = mTexture.getWidth(); + mTextureHeight = mTexture.getHeight(); + + mPaint = new Paint(); + mPaint.setDither(false); + } + + @Override + public void onDraw(Canvas canvas) { + final Bitmap texture = mTexture; + final Paint paint = mPaint; + + final int width = getWidth(); + final int height = getHeight(); + + final int textureWidth = mTextureWidth; + final int textureHeight = mTextureHeight; + + int x = 0; + int y; + + while (x < width) { + y = 0; + while (y < height) { + canvas.drawBitmap(texture, x, y, paint); + y += textureHeight; + } + x += textureWidth; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TrackingView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TrackingView.java new file mode 100644 index 0000000..c59eb6a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/TrackingView.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.Display; +import android.view.KeyEvent; +import android.view.WindowManager; +import android.widget.LinearLayout; + + +public class TrackingView extends LinearLayout { + final Display mDisplay; + PhoneStatusBarService mService; + boolean mTracking; + int mStartX, mStartY; + + public TrackingView(Context context, AttributeSet attrs) { + super(context, attrs); + mDisplay = ((WindowManager)context.getSystemService( + Context.WINDOW_SERVICE)).getDefaultDisplay(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mService.updateExpandedHeight(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_BACK: + if (down) { + //mService.deactivate(); + } + return true; + } + return super.dispatchKeyEvent(event); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mService.onTrackingViewAttached(); + } +} |