diff options
Diffstat (limited to 'packages/SystemUI/src')
22 files changed, 5418 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/CarrierLabel.java b/packages/SystemUI/src/com/android/systemui/statusbar/CarrierLabel.java new file mode 100644 index 0000000..d89d093 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CarrierLabel.java @@ -0,0 +1,117 @@ +/* + * 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.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.provider.Telephony; +import android.util.AttributeSet; +import android.util.Slog; +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 CarrierLabel extends TextView { + private boolean mAttached; + + public CarrierLabel(Context context) { + this(context, null); + } + + public CarrierLabel(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CarrierLabel(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + updateNetworkName(false, null, false, null); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mAttached) { + mAttached = true; + IntentFilter filter = new IntentFilter(); + filter.addAction(Telephony.Intents.SPN_STRINGS_UPDATED_ACTION); + getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); + } + } + + @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 (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)); + } + } + }; + + void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { + if (false) { + Slog.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn + + " showPlmn=" + showPlmn + " plmn=" + plmn); + } + StringBuilder str = new StringBuilder(); + boolean something = false; + if (showPlmn && plmn != null) { + str.append(plmn); + something = true; + } + if (showSpn && spn != null) { + if (something) { + str.append(' '); + } + str.append(spn); + something = true; + } + if (something) { + setText(str.toString()); + } else { + setText(com.android.internal.R.string.lockscreen_carrier_default); + } + } + + +} + + 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..9fc8df5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/Clock.java @@ -0,0 +1,208 @@ +/* + * 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.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.format.DateFormat; +import android.text.style.CharacterStyle; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StyleSpan; +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 boolean mAttached; + private Calendar mCalendar; + private String mClockFormatString; + private SimpleDateFormat mClockFormat; + + private static final int AM_PM_STYLE_NORMAL = 0; + private static final int AM_PM_STYLE_SMALL = 1; + private static final int AM_PM_STYLE_GONE = 2; + + private static final int AM_PM_STYLE = AM_PM_STYLE_GONE; + + 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, getHandler()); + } + + // 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. + */ + if (AM_PM_STYLE != AM_PM_STYLE_NORMAL) { + 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 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()); + + if (AM_PM_STYLE != AM_PM_STYLE_NORMAL) { + int magic1 = result.indexOf(MAGIC1); + int magic2 = result.indexOf(MAGIC2); + if (magic1 >= 0 && magic2 > magic1) { + SpannableStringBuilder formatted = new SpannableStringBuilder(result); + if (AM_PM_STYLE == AM_PM_STYLE_GONE) { + formatted.delete(magic1, magic2+1); + } else { + if (AM_PM_STYLE == AM_PM_STYLE_SMALL) { + CharacterStyle style = new RelativeSizeSpan(0.7f); + formatted.setSpan(style, magic1, magic2, + Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + formatted.delete(magic2, magic2 + 1); + formatted.delete(magic1, magic1 + 1); + } + return formatted; + } + } + + 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..f45caf5 --- /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 { + StatusBarService 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..f9347b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -0,0 +1,201 @@ +/* + * 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 com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarIconList; +import com.android.internal.statusbar.StatusBarNotification; + +/** + * 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; + 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..3d85f27 --- /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 { + StatusBarService 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(StatusBarService.TAG, "height changed old=" + mPrevHeight + // + " new=" + height); + mPrevHeight = height; + mService.updateExpandedViewPos(StatusBarService.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..027bed4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java @@ -0,0 +1,154 @@ +/* + * 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.util.Slog; +import android.view.View; +import android.widget.LinearLayout; + +import com.android.systemui.R; + + +public class IconMerger extends LinearLayout { + private static final String TAG = "IconMerger"; + + private StatusBarIconView mMoreView; + + public IconMerger(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void addMoreView(StatusBarIconView v, LinearLayout.LayoutParams lp) { + super.addView(v, lp); + mMoreView = v; + } + + public void addView(StatusBarIconView v, int index, LinearLayout.LayoutParams lp) { + if (index == 0) { + throw new RuntimeException("Attempt to put view before the more view: " + v); + } + super.addView(v, index, lp); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + 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 + final StatusBarIconView moreView = mMoreView; + int fitLeft = -1; + int startIndex = -1; + for (i=0; i<N; i++) { + final View child = getChildAt(i); + if (child == moreView) { + startIndex = i+1; + } + else if (child.getVisibility() != GONE) { + fitLeft = child.getLeft(); + break; + } + } + + if (moreView == null || startIndex < 0) { + return; + /* + throw new RuntimeException("Status Bar / IconMerger moreView == " + moreView + + " startIndex=" + startIndex); + */ + } + + // 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 StatusBarIconView child = (StatusBarIconView)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 = child.getStatusBarIcon().number; + 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/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java new file mode 100644 index 0000000..d98bd7d --- /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; + + 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); + } + + /** + * Returns whether the set succeeded. + */ + public boolean set(StatusBarIcon icon) { + final boolean iconEquals = mIcon != null + && streq(mIcon.iconPackage, icon.iconPackage) + && mIcon.iconId == icon.iconId; + final boolean levelEquals = iconEquals + && mIcon.iconLevel == icon.iconLevel; + final boolean visibilityEquals = mIcon != null + && mIcon.visible == icon.visible; + if (!iconEquals) { + Drawable drawable = getIcon(icon); + if (drawable == null) { + Slog.w(StatusBarService.TAG, "No icon for slot " + mSlot); + return false; + } + setImageDrawable(drawable); + } + if (!levelEquals) { + setImageLevel(icon.iconLevel); + } + if (!visibilityEquals) { + setVisibility(icon.visible ? VISIBLE : GONE); + } + mIcon = icon.clone(); + return true; + } + + private Drawable getIcon(StatusBarIcon icon) { + return getIcon(getContext(), icon); + } + + /** + * 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 + */ + public static Drawable getIcon(Context context, StatusBarIcon icon) { + Resources r = null; + + if (icon.iconPackage != null) { + try { + r = context.getPackageManager().getResourcesForApplication(icon.iconPackage); + } catch (PackageManager.NameNotFoundException ex) { + Slog.e(StatusBarService.TAG, "Icon package not found: " + icon.iconPackage); + return null; + } + } else { + r = context.getResources(); + } + + if (icon.iconId == 0) { + return null; + } + + try { + return r.getDrawable(icon.iconId); + } catch (RuntimeException e) { + Slog.w(StatusBarService.TAG, "Icon not found in " + + (icon.iconPackage != null ? icon.iconId : "<system>") + + ": " + Integer.toHexString(icon.iconId)); + } + + return null; + } + + public StatusBarIcon getStatusBarIcon() { + return mIcon; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java new file mode 100644 index 0000000..4f39ee4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java @@ -0,0 +1,1160 @@ +/* + * 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.policy; + +import android.app.StatusBarManager; +import android.app.AlertDialog; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothPbap; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.TypedArray; +import android.graphics.PixelFormat; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.location.LocationManager; +import android.media.AudioManager; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.net.wifi.WifiManager; +import android.os.Binder; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.storage.StorageManager; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.TelephonyManager; +import android.text.format.DateFormat; +import android.text.style.CharacterStyle; +import android.text.style.RelativeSizeSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.util.Slog; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowManagerImpl; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.IccCard; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.cdma.EriInfo; +import com.android.internal.telephony.cdma.TtyIntent; +import com.android.server.am.BatteryStatsService; + +import com.android.systemui.R; + +/** + * This class contains all of the policy about which icons are installed in the status + * bar at boot time. It goes through the normal API for icons, even though it probably + * strictly doesn't need to. + */ +public class StatusBarPolicy { + private static final String TAG = "StatusBarPolicy"; + + // message codes for the handler + private static final int EVENT_BATTERY_CLOSE = 4; + + private static final int AM_PM_STYLE_NORMAL = 0; + private static final int AM_PM_STYLE_SMALL = 1; + private static final int AM_PM_STYLE_GONE = 2; + + private static final int AM_PM_STYLE = AM_PM_STYLE_GONE; + + private final Context mContext; + private final StatusBarManager mService; + private final Handler mHandler = new StatusBarHandler(); + private final IBatteryStats mBatteryStats; + + // storage + private StorageManager mStorageManager; + + // battery + private boolean mBatteryFirst = true; + private boolean mBatteryPlugged; + private int mBatteryLevel; + private AlertDialog mLowBatteryDialog; + private TextView mBatteryLevelTextView; + private View mBatteryView; + private int mBatteryViewSequence; + private boolean mBatteryShowLowOnEndCall = false; + private static final boolean SHOW_LOW_BATTERY_WARNING = true; + private static final boolean SHOW_BATTERY_WARNINGS_IN_CALL = true; + + // phone + private TelephonyManager mPhone; + private int mPhoneSignalIconId; + + //***** Signal strength icons + //GSM/UMTS + private static final int[] sSignalImages = new int[] { + R.drawable.stat_sys_signal_0, + R.drawable.stat_sys_signal_1, + R.drawable.stat_sys_signal_2, + R.drawable.stat_sys_signal_3, + R.drawable.stat_sys_signal_4 + }; + private static final int[] sSignalImages_r = new int[] { + R.drawable.stat_sys_r_signal_0, + R.drawable.stat_sys_r_signal_1, + R.drawable.stat_sys_r_signal_2, + R.drawable.stat_sys_r_signal_3, + R.drawable.stat_sys_r_signal_4 + }; + private static final int[] sRoamingIndicatorImages_cdma = new int[] { + R.drawable.stat_sys_roaming_cdma_0, //Standard Roaming Indicator + // 1 is Standard Roaming Indicator OFF + // TODO T: image never used, remove and put 0 instead? + R.drawable.stat_sys_roaming_cdma_0, + + // 2 is Standard Roaming Indicator FLASHING + // TODO T: image never used, remove and put 0 instead? + R.drawable.stat_sys_roaming_cdma_0, + + // 3-12 Standard ERI + R.drawable.stat_sys_roaming_cdma_0, //3 + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + + // 13-63 Reserved for Standard ERI + R.drawable.stat_sys_roaming_cdma_0, //13 + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + + // 64-127 Reserved for Non Standard (Operator Specific) ERI + R.drawable.stat_sys_roaming_cdma_0, //64 + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0, + R.drawable.stat_sys_roaming_cdma_0 //83 + + // 128-255 Reserved + }; + + //***** Data connection icons + private int[] mDataIconList = sDataNetType_g; + //GSM/UMTS + private static final int[] sDataNetType_g = new int[] { + R.drawable.stat_sys_data_connected_g, + R.drawable.stat_sys_data_in_g, + R.drawable.stat_sys_data_out_g, + R.drawable.stat_sys_data_inandout_g, + }; + private static final int[] sDataNetType_3g = new int[] { + R.drawable.stat_sys_data_connected_3g, + R.drawable.stat_sys_data_in_3g, + R.drawable.stat_sys_data_out_3g, + R.drawable.stat_sys_data_inandout_3g, + }; + private static final int[] sDataNetType_e = new int[] { + R.drawable.stat_sys_data_connected_e, + R.drawable.stat_sys_data_in_e, + R.drawable.stat_sys_data_out_e, + R.drawable.stat_sys_data_inandout_e, + }; + //3.5G + private static final int[] sDataNetType_h = new int[] { + R.drawable.stat_sys_data_connected_h, + R.drawable.stat_sys_data_in_h, + R.drawable.stat_sys_data_out_h, + R.drawable.stat_sys_data_inandout_h, + }; + + //CDMA + // Use 3G icons for EVDO data and 1x icons for 1XRTT data + private static final int[] sDataNetType_1x = new int[] { + R.drawable.stat_sys_data_connected_1x, + R.drawable.stat_sys_data_in_1x, + R.drawable.stat_sys_data_out_1x, + R.drawable.stat_sys_data_inandout_1x, + }; + + // Assume it's all good unless we hear otherwise. We don't always seem + // to get broadcasts that it *is* there. + IccCard.State mSimState = IccCard.State.READY; + int mPhoneState = TelephonyManager.CALL_STATE_IDLE; + int mDataState = TelephonyManager.DATA_DISCONNECTED; + int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE; + ServiceState mServiceState; + SignalStrength mSignalStrength; + + // data connection + private boolean mDataIconVisible; + private boolean mHspaDataDistinguishable; + + // ringer volume + private boolean mVolumeVisible; + + // bluetooth device status + private int mBluetoothHeadsetState; + private boolean mBluetoothA2dpConnected; + private int mBluetoothPbapState; + private boolean mBluetoothEnabled; + + // wifi + private static final int[] sWifiSignalImages = new int[] { + R.drawable.stat_sys_wifi_signal_1, + R.drawable.stat_sys_wifi_signal_2, + R.drawable.stat_sys_wifi_signal_3, + R.drawable.stat_sys_wifi_signal_4, + }; + private static final int sWifiTemporarilyNotConnectedImage = + R.drawable.stat_sys_wifi_signal_0; + + private int mLastWifiSignalLevel = -1; + private boolean mIsWifiConnected = false; + + // sync state + // If sync is active the SyncActive icon is displayed. If sync is not active but + // sync is failing the SyncFailing icon is displayed. Otherwise neither are displayed. + + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { + updateBattery(intent); + } + else if (action.equals(Intent.ACTION_ALARM_CHANGED)) { + updateAlarm(intent); + } + else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) { + updateSyncState(intent); + } + else if (action.equals(Intent.ACTION_BATTERY_LOW)) { + onBatteryLow(intent); + } + else if (action.equals(Intent.ACTION_BATTERY_OKAY) + || action.equals(Intent.ACTION_POWER_CONNECTED)) { + onBatteryOkay(intent); + } + else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) || + action.equals(BluetoothHeadset.ACTION_STATE_CHANGED) || + action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED) || + action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { + updateBluetooth(intent); + } + else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) || + action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION) || + action.equals(WifiManager.RSSI_CHANGED_ACTION)) { + updateWifi(intent); + } + else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) || + action.equals(LocationManager.GPS_FIX_CHANGE_ACTION)) { + updateGps(intent); + } + else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) || + action.equals(AudioManager.VIBRATE_SETTING_CHANGED_ACTION)) { + updateVolume(); + } + else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) { + updateSimState(intent); + } + else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) { + updateTTY(intent); + } + } + }; + + public StatusBarPolicy(Context context) { + mContext = context; + mService = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE); + mSignalStrength = new SignalStrength(); + mBatteryStats = BatteryStatsService.getService(); + + // storage + mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + mStorageManager.registerListener( + new com.android.systemui.usb.StorageNotification(context)); + + // battery + mService.setIcon("battery", com.android.internal.R.drawable.stat_sys_battery_unknown, 0); + + // phone_signal + mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + mPhoneSignalIconId = R.drawable.stat_sys_signal_null; + mService.setIcon("phone_signal", mPhoneSignalIconId, 0); + + // register for phone state notifications. + ((TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE)) + .listen(mPhoneStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE + | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS + | PhoneStateListener.LISTEN_CALL_STATE + | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE + | PhoneStateListener.LISTEN_DATA_ACTIVITY); + + // data_connection + mService.setIcon("data_connection", R.drawable.stat_sys_data_connected_g, 0); + mService.setIconVisibility("data_connection", false); + + // wifi + mService.setIcon("wifi", sWifiSignalImages[0], 0); + mService.setIconVisibility("wifi", false); + // wifi will get updated by the sticky intents + + // TTY status + mService.setIcon("tty", R.drawable.stat_sys_tty_mode, 0); + mService.setIconVisibility("tty", false); + + // Cdma Roaming Indicator, ERI + mService.setIcon("cdma_eri", R.drawable.stat_sys_roaming_cdma_0, 0); + mService.setIconVisibility("cdma_eri", false); + + // bluetooth status + mService.setIcon("bluetooth", R.drawable.stat_sys_data_bluetooth, 0); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + mBluetoothEnabled = adapter.isEnabled(); + } else { + mBluetoothEnabled = false; + } + mBluetoothA2dpConnected = false; + mBluetoothHeadsetState = BluetoothHeadset.STATE_DISCONNECTED; + mBluetoothPbapState = BluetoothPbap.STATE_DISCONNECTED; + mService.setIconVisibility("bluetooth", mBluetoothEnabled); + + // Gps status + mService.setIcon("gps", R.drawable.stat_sys_gps_acquiring_anim, 0); + mService.setIconVisibility("gps", false); + + // Alarm clock + mService.setIcon("alarm_clock", R.drawable.stat_notify_alarm, 0); + mService.setIconVisibility("alarm_clock", false); + + // Sync state + mService.setIcon("sync_active", com.android.internal.R.drawable.stat_notify_sync_anim0, 0); + mService.setIcon("sync_failing", com.android.internal.R.drawable.stat_notify_sync_error, 0); + mService.setIconVisibility("sync_active", false); + mService.setIconVisibility("sync_failing", false); + + // volume + mService.setIcon("volume", R.drawable.stat_sys_ringer_silent, 0); + mService.setIconVisibility("volume", false); + updateVolume(); + + IntentFilter filter = new IntentFilter(); + + // Register for Intent broadcasts for... + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Intent.ACTION_BATTERY_LOW); + filter.addAction(Intent.ACTION_BATTERY_OKAY); + filter.addAction(Intent.ACTION_POWER_CONNECTED); + filter.addAction(Intent.ACTION_ALARM_CHANGED); + filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + filter.addAction(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); + filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + filter.addAction(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.RSSI_CHANGED_ACTION); + filter.addAction(LocationManager.GPS_ENABLED_CHANGE_ACTION); + filter.addAction(LocationManager.GPS_FIX_CHANGE_ACTION); + filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION); + mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + + // load config to determine if to distinguish Hspa data icon + try { + mHspaDataDistinguishable = mContext.getResources().getBoolean( + R.bool.config_hspa_data_distinguishable); + } catch (Exception e) { + mHspaDataDistinguishable = false; + } + } + + private final void updateAlarm(Intent intent) { + boolean alarmSet = intent.getBooleanExtra("alarmSet", false); + mService.setIconVisibility("alarm_clock", alarmSet); + } + + private final void updateSyncState(Intent intent) { + boolean isActive = intent.getBooleanExtra("active", false); + boolean isFailing = intent.getBooleanExtra("failing", false); + mService.setIconVisibility("sync_active", isActive); + // Don't display sync failing icon: BUG 1297963 Set sync error timeout to "never" + //mService.setIconVisibility("sync_failing", isFailing && !isActive); + } + + private final void updateBattery(Intent intent) { + final int id = intent.getIntExtra("icon-small", 0); + int level = intent.getIntExtra("level", 0); + mService.setIcon("battery", id, level); + + boolean plugged = intent.getIntExtra("plugged", 0) != 0; + level = intent.getIntExtra("level", -1); + if (false) { + Slog.d(TAG, "updateBattery level=" + level + + " plugged=" + plugged + + " mBatteryPlugged=" + mBatteryPlugged + + " mBatteryLevel=" + mBatteryLevel + + " mBatteryFirst=" + mBatteryFirst); + } + + boolean oldPlugged = mBatteryPlugged; + + mBatteryPlugged = plugged; + mBatteryLevel = level; + + if (mBatteryFirst) { + mBatteryFirst = false; + } + /* + * No longer showing the battery view because it draws attention away + * from the USB storage notification. We could still show it when + * connected to a brick, but that could lead to the user into thinking + * the device does not charge when plugged into USB (since he/she would + * not see the same battery screen on USB as he sees on brick). + */ + if (false) { + Slog.d(TAG, "plugged=" + plugged + " oldPlugged=" + oldPlugged + " level=" + level); + } + } + + private void onBatteryLow(Intent intent) { + if (SHOW_LOW_BATTERY_WARNING) { + if (false) { + Slog.d(TAG, "mPhoneState=" + mPhoneState + + " mLowBatteryDialog=" + mLowBatteryDialog + + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall); + } + + if (SHOW_BATTERY_WARNINGS_IN_CALL || mPhoneState == TelephonyManager.CALL_STATE_IDLE) { + showLowBatteryWarning(); + } else { + mBatteryShowLowOnEndCall = true; + } + } + } + + private void onBatteryOkay(Intent intent) { + if (mLowBatteryDialog != null + && SHOW_LOW_BATTERY_WARNING) { + mLowBatteryDialog.dismiss(); + mBatteryShowLowOnEndCall = false; + } + } + + private void setBatteryLevel(View parent, int id, int height, int background, int level) { + ImageView v = (ImageView)parent.findViewById(id); + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)v.getLayoutParams(); + lp.weight = height; + if (background != 0) { + v.setBackgroundResource(background); + Drawable bkg = v.getBackground(); + bkg.setLevel(level); + } + } + + private void showLowBatteryWarning() { + closeLastBatteryView(); + + // Show exact battery level. + CharSequence levelText = mContext.getString( + R.string.battery_low_percent_format, mBatteryLevel); + + if (mBatteryLevelTextView != null) { + mBatteryLevelTextView.setText(levelText); + } else { + View v = View.inflate(mContext, R.layout.battery_low, null); + mBatteryLevelTextView=(TextView)v.findViewById(R.id.level_percent); + + mBatteryLevelTextView.setText(levelText); + + AlertDialog.Builder b = new AlertDialog.Builder(mContext); + b.setCancelable(true); + b.setTitle(R.string.battery_low_title); + b.setView(v); + b.setIcon(android.R.drawable.ic_dialog_alert); + b.setPositiveButton(android.R.string.ok, null); + + final Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_NO_HISTORY); + if (intent.resolveActivity(mContext.getPackageManager()) != null) { + b.setNegativeButton(R.string.battery_low_why, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mContext.startActivity(intent); + if (mLowBatteryDialog != null) { + mLowBatteryDialog.dismiss(); + } + } + }); + } + + AlertDialog d = b.create(); + d.setOnDismissListener(mLowBatteryListener); + d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + d.show(); + mLowBatteryDialog = d; + } + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.System.getInt(cr, + Settings.System.POWER_SOUNDS_ENABLED, 1) == 1) + { + final String soundPath = Settings.System.getString(cr, + Settings.System.LOW_BATTERY_SOUND); + if (soundPath != null) { + final Uri soundUri = Uri.parse("file://" + soundPath); + if (soundUri != null) { + final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); + if (sfx != null) { + sfx.setStreamType(AudioManager.STREAM_SYSTEM); + sfx.play(); + } + } + } + } + } + + private final void updateCallState(int state) { + mPhoneState = state; + if (false) { + Slog.d(TAG, "mPhoneState=" + mPhoneState + + " mLowBatteryDialog=" + mLowBatteryDialog + + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall); + } + if (mPhoneState == TelephonyManager.CALL_STATE_IDLE) { + if (mBatteryShowLowOnEndCall) { + if (!mBatteryPlugged) { + showLowBatteryWarning(); + } + mBatteryShowLowOnEndCall = false; + } + } else { + if (mLowBatteryDialog != null) { + mLowBatteryDialog.dismiss(); + mBatteryShowLowOnEndCall = true; + } + } + } + + private DialogInterface.OnDismissListener mLowBatteryListener + = new DialogInterface.OnDismissListener() { + public void onDismiss(DialogInterface dialog) { + mLowBatteryDialog = null; + mBatteryLevelTextView = null; + } + }; + + private void scheduleCloseBatteryView() { + Message m = mHandler.obtainMessage(EVENT_BATTERY_CLOSE); + m.arg1 = (++mBatteryViewSequence); + mHandler.sendMessageDelayed(m, 3000); + } + + private void closeLastBatteryView() { + if (mBatteryView != null) { + //mBatteryView.debug(); + WindowManagerImpl.getDefault().removeView(mBatteryView); + mBatteryView = null; + } + } + + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + mSignalStrength = signalStrength; + updateSignalStrength(); + } + + @Override + public void onServiceStateChanged(ServiceState state) { + mServiceState = state; + updateSignalStrength(); + updateCdmaRoamingIcon(state); + updateDataIcon(); + } + + @Override + public void onCallStateChanged(int state, String incomingNumber) { + updateCallState(state); + // In cdma, if a voice call is made, RSSI should switch to 1x. + if (isCdma()) { + updateSignalStrength(); + } + } + + @Override + public void onDataConnectionStateChanged(int state, int networkType) { + mDataState = state; + updateDataNetType(networkType); + updateDataIcon(); + } + + @Override + public void onDataActivity(int direction) { + mDataActivity = direction; + updateDataIcon(); + } + }; + + private final void updateSimState(Intent intent) { + String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE); + if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { + mSimState = IccCard.State.ABSENT; + } + else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) { + mSimState = IccCard.State.READY; + } + else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) { + final String lockedReason = intent.getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON); + if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { + mSimState = IccCard.State.PIN_REQUIRED; + } + else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { + mSimState = IccCard.State.PUK_REQUIRED; + } + else { + mSimState = IccCard.State.NETWORK_LOCKED; + } + } else { + mSimState = IccCard.State.UNKNOWN; + } + updateDataIcon(); + } + + private boolean isCdma() { + return (mSignalStrength != null) && !mSignalStrength.isGsm(); + } + + private boolean isEvdo() { + return ( (mServiceState != null) + && ((mServiceState.getRadioTechnology() + == ServiceState.RADIO_TECHNOLOGY_EVDO_0) + || (mServiceState.getRadioTechnology() + == ServiceState.RADIO_TECHNOLOGY_EVDO_A) + || (mServiceState.getRadioTechnology() + == ServiceState.RADIO_TECHNOLOGY_EVDO_B))); + } + + private boolean hasService() { + if (mServiceState != null) { + switch (mServiceState.getState()) { + case ServiceState.STATE_OUT_OF_SERVICE: + case ServiceState.STATE_POWER_OFF: + return false; + default: + return true; + } + } else { + return false; + } + } + + private final void updateSignalStrength() { + int iconLevel = -1; + int[] iconList; + + // Display signal strength while in "emergency calls only" mode + if (!hasService() && !mServiceState.isEmergencyOnly()) { + //Slog.d(TAG, "updateSignalStrength: no service"); + if (Settings.System.getInt(mContext.getContentResolver(), + Settings.System.AIRPLANE_MODE_ON, 0) == 1) { + mPhoneSignalIconId = R.drawable.stat_sys_signal_flightmode; + } else { + mPhoneSignalIconId = R.drawable.stat_sys_signal_null; + } + mService.setIcon("phone_signal", mPhoneSignalIconId, 0); + return; + } + + if (!isCdma()) { + int asu = mSignalStrength.getGsmSignalStrength(); + + // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 + // asu = 0 (-113dB or less) is very weak + // signal, its better to show 0 bars to the user in such cases. + // asu = 99 is a special case, where the signal strength is unknown. + if (asu <= 2 || asu == 99) iconLevel = 0; + else if (asu >= 12) iconLevel = 4; + else if (asu >= 8) iconLevel = 3; + else if (asu >= 5) iconLevel = 2; + else iconLevel = 1; + + // Though mPhone is a Manager, this call is not an IPC + if (mPhone.isNetworkRoaming()) { + iconList = sSignalImages_r; + } else { + iconList = sSignalImages; + } + } else { + iconList = this.sSignalImages; + + // If 3G(EV) and 1x network are available than 3G should be + // displayed, displayed RSSI should be from the EV side. + // If a voice call is made then RSSI should switch to 1x. + if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo()){ + iconLevel = getEvdoLevel(); + if (false) { + Slog.d(TAG, "use Evdo level=" + iconLevel + " to replace Cdma Level=" + getCdmaLevel()); + } + } else { + iconLevel = getCdmaLevel(); + } + } + mPhoneSignalIconId = iconList[iconLevel]; + mService.setIcon("phone_signal", mPhoneSignalIconId, 0); + } + + private int getCdmaLevel() { + final int cdmaDbm = mSignalStrength.getCdmaDbm(); + final int cdmaEcio = mSignalStrength.getCdmaEcio(); + int levelDbm = 0; + int levelEcio = 0; + + if (cdmaDbm >= -75) levelDbm = 4; + else if (cdmaDbm >= -85) levelDbm = 3; + else if (cdmaDbm >= -95) levelDbm = 2; + else if (cdmaDbm >= -100) levelDbm = 1; + else levelDbm = 0; + + // Ec/Io are in dB*10 + if (cdmaEcio >= -90) levelEcio = 4; + else if (cdmaEcio >= -110) levelEcio = 3; + else if (cdmaEcio >= -130) levelEcio = 2; + else if (cdmaEcio >= -150) levelEcio = 1; + else levelEcio = 0; + + return (levelDbm < levelEcio) ? levelDbm : levelEcio; + } + + private int getEvdoLevel() { + int evdoDbm = mSignalStrength.getEvdoDbm(); + int evdoSnr = mSignalStrength.getEvdoSnr(); + int levelEvdoDbm = 0; + int levelEvdoSnr = 0; + + if (evdoDbm >= -65) levelEvdoDbm = 4; + else if (evdoDbm >= -75) levelEvdoDbm = 3; + else if (evdoDbm >= -90) levelEvdoDbm = 2; + else if (evdoDbm >= -105) levelEvdoDbm = 1; + else levelEvdoDbm = 0; + + if (evdoSnr >= 7) levelEvdoSnr = 4; + else if (evdoSnr >= 5) levelEvdoSnr = 3; + else if (evdoSnr >= 3) levelEvdoSnr = 2; + else if (evdoSnr >= 1) levelEvdoSnr = 1; + else levelEvdoSnr = 0; + + return (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr; + } + + private final void updateDataNetType(int net) { + switch (net) { + case TelephonyManager.NETWORK_TYPE_EDGE: + mDataIconList = sDataNetType_e; + break; + case TelephonyManager.NETWORK_TYPE_UMTS: + mDataIconList = sDataNetType_3g; + break; + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + if (mHspaDataDistinguishable) { + mDataIconList = sDataNetType_h; + } else { + mDataIconList = sDataNetType_3g; + } + break; + case TelephonyManager.NETWORK_TYPE_CDMA: + // display 1xRTT for IS95A/B + mDataIconList = this.sDataNetType_1x; + break; + case TelephonyManager.NETWORK_TYPE_1xRTT: + mDataIconList = this.sDataNetType_1x; + break; + case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + mDataIconList = sDataNetType_3g; + break; + default: + mDataIconList = sDataNetType_g; + break; + } + } + + private final void updateDataIcon() { + int iconId; + boolean visible = true; + + if (!isCdma()) { + // GSM case, we have to check also the sim state + if (mSimState == IccCard.State.READY || mSimState == IccCard.State.UNKNOWN) { + if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) { + switch (mDataActivity) { + case TelephonyManager.DATA_ACTIVITY_IN: + iconId = mDataIconList[1]; + break; + case TelephonyManager.DATA_ACTIVITY_OUT: + iconId = mDataIconList[2]; + break; + case TelephonyManager.DATA_ACTIVITY_INOUT: + iconId = mDataIconList[3]; + break; + default: + iconId = mDataIconList[0]; + break; + } + mService.setIcon("data_connection", iconId, 0); + } else { + visible = false; + } + } else { + iconId = R.drawable.stat_sys_no_sim; + mService.setIcon("data_connection", iconId, 0); + } + } else { + // CDMA case, mDataActivity can be also DATA_ACTIVITY_DORMANT + if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) { + switch (mDataActivity) { + case TelephonyManager.DATA_ACTIVITY_IN: + iconId = mDataIconList[1]; + break; + case TelephonyManager.DATA_ACTIVITY_OUT: + iconId = mDataIconList[2]; + break; + case TelephonyManager.DATA_ACTIVITY_INOUT: + iconId = mDataIconList[3]; + break; + case TelephonyManager.DATA_ACTIVITY_DORMANT: + default: + iconId = mDataIconList[0]; + break; + } + mService.setIcon("data_connection", iconId, 0); + } else { + visible = false; + } + } + + long ident = Binder.clearCallingIdentity(); + try { + mBatteryStats.notePhoneDataConnectionState(mPhone.getNetworkType(), visible); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } + + if (mDataIconVisible != visible) { + mService.setIconVisibility("data_connection", visible); + mDataIconVisible = visible; + } + } + + private final void updateVolume() { + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + final int ringerMode = audioManager.getRingerMode(); + final boolean visible = ringerMode == AudioManager.RINGER_MODE_SILENT || + ringerMode == AudioManager.RINGER_MODE_VIBRATE; + final int iconId = audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER) + ? R.drawable.stat_sys_ringer_vibrate + : R.drawable.stat_sys_ringer_silent; + + if (visible) { + mService.setIcon("volume", iconId, 0); + } + if (visible != mVolumeVisible) { + mService.setIconVisibility("volume", visible); + mVolumeVisible = visible; + } + } + + private final void updateBluetooth(Intent intent) { + int iconId = R.drawable.stat_sys_data_bluetooth; + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + mBluetoothEnabled = state == BluetoothAdapter.STATE_ON; + } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { + mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, + BluetoothHeadset.STATE_ERROR); + } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { + BluetoothA2dp a2dp = new BluetoothA2dp(mContext); + if (a2dp.getConnectedSinks().size() != 0) { + mBluetoothA2dpConnected = true; + } else { + mBluetoothA2dpConnected = false; + } + } else if (action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { + mBluetoothPbapState = intent.getIntExtra(BluetoothPbap.PBAP_STATE, + BluetoothPbap.STATE_DISCONNECTED); + } else { + return; + } + + if (mBluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED || mBluetoothA2dpConnected || + mBluetoothPbapState == BluetoothPbap.STATE_CONNECTED) { + iconId = R.drawable.stat_sys_data_bluetooth_connected; + } + + mService.setIcon("bluetooth", iconId, 0); + mService.setIconVisibility("bluetooth", mBluetoothEnabled); + } + + private final void updateWifi(Intent intent) { + final String action = intent.getAction(); + if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + + final boolean enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED; + + if (!enabled) { + // If disabled, hide the icon. (We show icon when connected.) + mService.setIconVisibility("wifi", false); + } + + } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) { + final boolean enabled = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, + false); + if (!enabled) { + mService.setIconVisibility("wifi", false); + } + } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + + final NetworkInfo networkInfo = (NetworkInfo) + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + + int iconId; + if (networkInfo != null && networkInfo.isConnected()) { + mIsWifiConnected = true; + if (mLastWifiSignalLevel == -1) { + iconId = sWifiSignalImages[0]; + } else { + iconId = sWifiSignalImages[mLastWifiSignalLevel]; + } + + // Show the icon since wi-fi is connected + mService.setIconVisibility("wifi", true); + + } else { + mLastWifiSignalLevel = -1; + mIsWifiConnected = false; + iconId = sWifiSignalImages[0]; + + // Hide the icon since we're not connected + mService.setIconVisibility("wifi", false); + } + + mService.setIcon("wifi", iconId, 0); + } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { + int iconId; + final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); + int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, + sWifiSignalImages.length); + if (newSignalLevel != mLastWifiSignalLevel) { + mLastWifiSignalLevel = newSignalLevel; + if (mIsWifiConnected) { + iconId = sWifiSignalImages[newSignalLevel]; + } else { + iconId = sWifiTemporarilyNotConnectedImage; + } + mService.setIcon("wifi", iconId, 0); + } + } + } + + private final void updateGps(Intent intent) { + final String action = intent.getAction(); + final boolean enabled = intent.getBooleanExtra(LocationManager.EXTRA_GPS_ENABLED, false); + + if (action.equals(LocationManager.GPS_FIX_CHANGE_ACTION) && enabled) { + // GPS is getting fixes + mService.setIcon("gps", com.android.internal.R.drawable.stat_sys_gps_on, 0); + mService.setIconVisibility("gps", true); + } else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) && !enabled) { + // GPS is off + mService.setIconVisibility("gps", false); + } else { + // GPS is on, but not receiving fixes + mService.setIcon("gps", R.drawable.stat_sys_gps_acquiring_anim, 0); + mService.setIconVisibility("gps", true); + } + } + + private final void updateTTY(Intent intent) { + final String action = intent.getAction(); + final boolean enabled = intent.getBooleanExtra(TtyIntent.TTY_ENABLED, false); + + if (false) Slog.v(TAG, "updateTTY: enabled: " + enabled); + + if (enabled) { + // TTY is on + if (false) Slog.v(TAG, "updateTTY: set TTY on"); + mService.setIcon("tty", R.drawable.stat_sys_tty_mode, 0); + mService.setIconVisibility("tty", true); + } else { + // TTY is off + if (false) Slog.v(TAG, "updateTTY: set TTY off"); + mService.setIconVisibility("tty", false); + } + } + + private final void updateCdmaRoamingIcon(ServiceState state) { + if (!hasService()) { + mService.setIconVisibility("cdma_eri", false); + return; + } + + if (!isCdma()) { + mService.setIconVisibility("cdma_eri", false); + return; + } + + int[] iconList = sRoamingIndicatorImages_cdma; + int iconIndex = state.getCdmaEriIconIndex(); + int iconMode = state.getCdmaEriIconMode(); + + if (iconIndex == -1) { + Slog.e(TAG, "getCdmaEriIconIndex returned null, skipping ERI icon update"); + return; + } + + if (iconMode == -1) { + Slog.e(TAG, "getCdmeEriIconMode returned null, skipping ERI icon update"); + return; + } + + if (iconIndex == EriInfo.ROAMING_INDICATOR_OFF) { + if (false) Slog.v(TAG, "Cdma ROAMING_INDICATOR_OFF, removing ERI icon"); + mService.setIconVisibility("cdma_eri", false); + return; + } + + switch (iconMode) { + case EriInfo.ROAMING_ICON_MODE_NORMAL: + mService.setIcon("cdma_eri", iconList[iconIndex], 0); + mService.setIconVisibility("cdma_eri", true); + break; + case EriInfo.ROAMING_ICON_MODE_FLASH: + mService.setIcon("cdma_eri", R.drawable.stat_sys_roaming_cdma_flash, 0); + mService.setIconVisibility("cdma_eri", true); + break; + + } + mService.setIcon("phone_signal", mPhoneSignalIconId, 0); + } + + + private class StatusBarHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_BATTERY_CLOSE: + if (msg.arg1 == mBatteryViewSequence) { + closeLastBatteryView(); + } + break; + } + } + } +} 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..07bcce7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java @@ -0,0 +1,1606 @@ +/* + * 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 com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarIconList; +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.ServiceManager; +import android.os.SystemClock; +import android.util.Slog; +import android.util.Log; +import android.view.Display; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManagerImpl; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.ImageView; +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; +import com.android.systemui.statusbar.policy.StatusBarPolicy; + + + +public class StatusBarService extends Service implements CommandQueue.Callbacks { + static final String TAG = "StatusBarService"; + 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; + private static final int MSG_SHOW_INTRUDER = 1002; + private static final int MSG_HIDE_INTRUDER = 1003; + + // will likely move to a resource or other tunable param at some point + private static final int INTRUDER_ALERT_DECAY_MS = 10000; + + StatusBarPolicy mIconPolicy; + + CommandQueue mCommandQueue; + IStatusBarService mBarService; + + int mIconSize; + Display mDisplay; + StatusBarView mStatusBarView; + int mPixelFormat; + H mHandler = new H(); + Object mQueueLock = new Object(); + + // icons + LinearLayout mIcons; + IconMerger mNotificationIcons; + LinearLayout mStatusIcons; + + // expanded notifications + Dialog mExpandedDialog; + ExpandedView mExpandedView; + WindowManager.LayoutParams mExpandedParams; + ScrollView mScrollView; + View mNotificationLinearLayout; + View mExpandedContents; + // top bar + TextView mNoNotificationsTitle; + 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. + private boolean mPanelSlightlyVisible; + + // 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; + + 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); + } + } + + + @Override + public void onCreate() { + // First set up our views and stuff. + mDisplay = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + makeStatusBarView(this); + + // Connect in to the status bar manager service + StatusBarIconList iconList = new StatusBarIconList(); + ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); + ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); + mCommandQueue = new CommandQueue(this, iconList); + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + try { + mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications); + } catch (RemoteException ex) { + // If the system process isn't there we're doomed anyway. + } + + // Set up the initial icon state + int N = iconList.size(); + int viewIndex = 0; + for (int i=0; i<N; i++) { + StatusBarIcon icon = iconList.getIcon(i); + if (icon != null) { + addIcon(iconList.getSlot(i), i, viewIndex, icon); + viewIndex++; + } + } + + // Set up the initial notification state + N = notificationKeys.size(); + if (N == notifications.size()) { + for (int i=0; i<N; i++) { + addNotification(notificationKeys.get(i), notifications.get(i)); + } + } else { + Log.wtf(TAG, "Notification list length mismatch: keys=" + N + + " notifications=" + notifications.size()); + } + + // Put up the view + addStatusBarView(); + + // Lastly, call to the icon policy to install/update all the icons. + mIconPolicy = new StatusBarPolicy(this); + } + + @Override + public void onDestroy() { + // we're never destroyed + } + + // for immersive activities + private View mIntruderAlertView; + + /** + * Nobody binds to us. + */ + @Override + public IBinder onBind(Intent intent) { + return null; + } + + // ================================================================================ + // Constructing the view + // ================================================================================ + private void makeStatusBarView(Context context) { + Resources res = context.getResources(); + + mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); + + ExpandedView expanded = (ExpandedView)View.inflate(context, + R.layout.status_bar_expanded, null); + expanded.mService = this; + + mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null); + mIntruderAlertView.setVisibility(View.GONE); + mIntruderAlertView.setClickable(true); + + 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); + 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); + 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); + + // the more notifications icon + StatusBarIconView moreView = new StatusBarIconView(this, "more"); + moreView.set(new StatusBarIcon(null, R.drawable.stat_notify_more, 0)); + mNotificationIcons.addMoreView(moreView, + new LinearLayout.LayoutParams(mIconSize, mIconSize)); + + // set the inital view visibility + setAreThereNotifications(); + mDateView.setVisibility(View.INVISIBLE); + + // receive broadcasts + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_SCREEN_OFF); + context.registerReceiver(mBroadcastReceiver, filter); + } + + protected void addStatusBarView() { + Resources res = getResources(); + final int height= res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + + final StatusBarView view = mStatusBarView; + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + height, + 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); + + lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + PixelFormat.TRANSLUCENT); + lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; + lp.y += height * 1.5; // FIXME + lp.setTitle("IntruderAlert"); + lp.windowAnimations = com.android.internal.R.style.Animation_StatusBar_IntruderAlert; + + WindowManagerImpl.getDefault().addView(mIntruderAlertView, lp); + } + + public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { + if (SPEW) Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex + + " icon=" + icon); + StatusBarIconView view = new StatusBarIconView(this, slot); + view.set(icon); + mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize)); + } + + public void updateIcon(String slot, int index, int viewIndex, + StatusBarIcon old, StatusBarIcon icon) { + if (SPEW) Slog.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex + + " old=" + old + " icon=" + icon); + StatusBarIconView view = (StatusBarIconView)mStatusIcons.getChildAt(viewIndex); + view.set(icon); + } + + public void removeIcon(String slot, int index, int viewIndex) { + if (SPEW) Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex); + mStatusIcons.removeViewAt(viewIndex); + } + + public void addNotification(IBinder key, StatusBarNotification notification) { + StatusBarIconView iconView = addNotificationViews(key, notification); + if (iconView == null) return; + + boolean immersive = false; + try { + immersive = ActivityManagerNative.getDefault().isTopActivityImmersive(); + Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); + } catch (RemoteException ex) { + } + if (immersive) { + if ((notification.notification.flags & Notification.FLAG_HIGH_PRIORITY) != 0) { + Slog.d(TAG, "Presenting high-priority notification in immersive activity"); + // @@@ special new transient ticker mode + // 1. Populate mIntruderAlertView + + ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon); + TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText); + alertIcon.setImageDrawable(StatusBarIconView.getIcon( + alertIcon.getContext(), + iconView.getStatusBarIcon())); + alertText.setText(notification.notification.tickerText); + + View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content); + button.setOnClickListener( + new Launcher(notification.notification.contentIntent, + notification.pkg, notification.tag, notification.id)); + + // 2. Animate mIntruderAlertView in + mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER); + + // 3. Set alarm to age the notification off (TODO) + mHandler.removeMessages(MSG_HIDE_INTRUDER); + mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS); + } + } else if (notification.notification.fullScreenIntent != null) { + // not immersive & a full-screen alert should be shown + Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;" + + " sending fullScreenIntent"); + try { + notification.notification.fullScreenIntent.send(); + } catch (PendingIntent.CanceledException e) { + } + } else { + // usual case: status bar visible & not immersive + + // show the ticker + tick(notification); + } + + // Recalculate the position of the sliding windows and the titles. + setAreThereNotifications(); + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); + } + + public void updateNotification(IBinder key, StatusBarNotification notification) { + 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; + + if (false) { + Slog.d(TAG, "old notification: when=" + oldNotification.notification.when + + " ongoing=" + oldNotification.isOngoing() + + " expanded=" + oldEntry.expanded + + " contentView=" + oldContentView); + Slog.d(TAG, "new notification: when=" + notification.notification.when + + " ongoing=" + oldNotification.isOngoing() + + " contentView=" + contentView); + } + + // Can we just reapply the RemoteViews in place? If when didn't change, the order + // didn't change. + 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()) { + if (SPEW) 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)); + } + // Update the icon. + final StatusBarIcon ic = new StatusBarIcon(notification.pkg, + notification.notification.icon, notification.notification.iconLevel, + notification.notification.number); + if (!oldEntry.icon.set(ic)) { + handleNotificationError(key, notification, "Couldn't update icon: " + ic); + return; + } + } + catch (RuntimeException e) { + // It failed to add cleanly. Log, and remove the view from the panel. + Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); + removeNotificationViews(key); + addNotificationViews(key, notification); + } + } else { + if (SPEW) Slog.d(TAG, "not reusing notification"); + removeNotificationViews(key); + addNotificationViews(key, notification); + } + + // Restart the ticker if it's still running + tick(notification); + + // Recalculate the position of the sliding windows and the titles. + setAreThereNotifications(); + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); + } + + public void removeNotification(IBinder key) { + if (SPEW) Slog.d(TAG, "removeNotification key=" + key); + StatusBarNotification old = removeNotificationViews(key); + + if (old != null) { + // Cancel the ticker if it's still running + mTicker.removeEntry(old); + + // Recalculate the position of the sliding windows and the titles. + setAreThereNotifications(); + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); + } + } + + private int chooseIconIndex(boolean isOngoing, int viewIndex) { + final int latestSize = mLatest.size(); + if (isOngoing) { + return latestSize + (mOngoing.size() - viewIndex); + } else { + return latestSize - viewIndex; + } + } + + 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(R.layout.status_bar_latest_event, parent, false); + + // bind the click event to the content area + ViewGroup content = (ViewGroup)row.findViewById(R.id.content); + content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + content.setOnFocusChangeListener(mFocusChangeListener); + PendingIntent contentIntent = n.contentIntent; + if (contentIntent != null) { + 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) { + String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id); + Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); + return null; + } else { + content.addView(expanded); + row.setDrawingCacheEnabled(true); + } + + return new View[] { row, content, expanded }; + } + + StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { + 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); + if (views == null) { + handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " + + notification); + return null; + } + final View row = views[0]; + final View content = views[1]; + final View expanded = views[2]; + // Construct the icon. + final StatusBarIconView iconView = new StatusBarIconView(this, + notification.pkg + "/0x" + Integer.toHexString(notification.id)); + final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon, + notification.notification.iconLevel, notification.notification.number); + if (!iconView.set(ic)) { + handleNotificationError(key, notification, "Coulding create icon: " + ic); + return null; + } + // 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(mIconSize, mIconSize)); + return iconView; + } + + StatusBarNotification 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 null; + } + } + // Remove the expanded view. + ((ViewGroup)entry.row.getParent()).removeView(entry.row); + // Remove the icon. + ((ViewGroup)entry.icon.getParent()).removeView(entry.icon); + + return entry.notification; + } + + 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; + case MSG_SHOW_INTRUDER: + setIntruderAlertVisibility(true); + break; + case MSG_HIDE_INTRUDER: + setIntruderAlertVisibility(false); + 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) { + } + + if (mIntent != null) { + int[] pos = new int[2]; + v.getLocationOnScreen(pos); + Intent overlay = new Intent(); + overlay.setSourceBounds( + new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); + try { + mIntent.send(StatusBarService.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. + } + + // close the shade if it was open + animateCollapse(); + + // If this click was on the intruder alert, hide that instead + mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); + } + } + + private void tick(StatusBarNotification n) { + // Show the ticker if one is requested. Also don't do this + // until status bar window is attached to the window manager, + // because... well, what's the point otherwise? And trying to + // run a ticker without being attached will crash! + if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) { + if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS + | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { + mTicker.addEntry(n); + } + } + } + + /** + * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService + * about the failure. + * + * WARNING: this will call back into us. Don't hold any locks. + */ + void handleNotificationError(IBinder key, StatusBarNotification n, String message) { + removeNotification(key); + try { + mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); + } catch (RemoteException ex) { + // The end is nigh. + } + } + + private class MyTicker extends Ticker { + MyTicker(Context context, 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(StatusBarService.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; + } + mExpandedDialog.getWindow().setAttributes(mExpandedParams); + + // As long as this isn't just a repositioning that's not supposed to affect + // the user's perception of what's showing, call to say that the visibility + // has changed. (Otherwise, someone else will call to do that). + if (expandedPosition != EXPANDED_LEAVE_ALONE) { + if (SPEW) Slog.d(TAG, "updateExpandedViewPos visibilityChanged(" + visible + ")"); + visibilityChanged(visible); + } + } + + if (SPEW) { + Slog.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition + + " mTrackingParams.y=" + mTrackingParams.y + + " mTrackingPosition=" + mTrackingPosition + + " mExpandedParams.y=" + mExpandedParams.y + + " mExpandedParams.height=" + mExpandedParams.height); + } + } + + int getExpandedHeight() { + 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) + */ + void visibilityChanged(boolean visible) { + if (mPanelSlightlyVisible != visible) { + mPanelSlightlyVisible = visible; + try { + mBarService.onPanelRevealed(); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + } + } + + void performDisableActions(int net) { + int old = mDisabled; + int diff = net ^ old; + mDisabled = net; + + // act accordingly + if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { + if ((net & StatusBarManager.DISABLE_EXPAND) != 0) { + Slog.d(TAG, "DISABLE_EXPAND: yes"); + animateCollapse(); + } + } + if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { + Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); + if (mTicking) { + mNotificationIcons.setVisibility(View.INVISIBLE); + mTicker.halt(); + } else { + setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out); + } + } else { + Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); + if (!mExpandedVisible) { + setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); + } + } + } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { + if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { + mTicker.halt(); + } + } + } + + private View.OnClickListener mClearButtonListener = new View.OnClickListener() { + public void onClick(View v) { + try { + mBarService.onClearAllNotifications(); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + animateCollapse(); + } + }; + + private 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 (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { + updateResources(); + } + } + }; + + private void setIntruderAlertVisibility(boolean vis) { + mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE); + } + + /** + * Reload some of our resources when the configuration changes. + * + * We don't reload everything when the configuration changes -- we probably + * should, but getting that smooth is tough. Someday we'll fix that. In the + * meantime, just update the things that we know change. + */ + void updateResources() { + 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/StatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarView.java new file mode 100644 index 0000000..1e140b9 --- /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; + + StatusBarService 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(StatusBarService.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..07e8653 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java @@ -0,0 +1,273 @@ +/* + * 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.StatusBarIcon; +import com.android.internal.statusbar.StatusBarNotification; +import com.android.internal.util.CharSequences; +import com.android.systemui.R; + +public abstract class Ticker { + private static final int TICKER_SEGMENT_DELAY = 3000; + + private Context mContext; + private Handler mHandler = new Handler(); + private ArrayList<Segment> mSegments = new ArrayList(); + private TextPaint mPaint; + private View mTickerView; + private ImageSwitcher mIconSwitcher; + private TextSwitcher mTextSwitcher; + + private final class Segment { + StatusBarNotification notification; + 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.notification = 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; + } + }; + + Ticker(Context context, StatusBarView sb) { + mContext = context; + 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) { + int initialCount = mSegments.size(); + + // If what's being displayed has the same text and icon, just drop it + // (which will let the current one finish, this happens when apps do + // a notification storm). + if (initialCount > 0) { + final Segment seg = mSegments.get(0); + if (n.pkg.equals(seg.notification.pkg) + && n.notification.icon == seg.notification.notification.icon + && n.notification.iconLevel == seg.notification.notification.iconLevel + && CharSequences.equals(seg.notification.notification.tickerText, + n.notification.tickerText)) { + return; + } + } + + final Drawable icon = StatusBarIconView.getIcon(mContext, + new StatusBarIcon(n.pkg, n.notification.icon, n.notification.iconLevel, 0)); + final Segment newSegment = new Segment(n, icon, n.notification.tickerText); + + // If there's already a notification schedule for this package and id, remove it. + for (int i=0; i<initialCount; i++) { + Segment seg = mSegments.get(i); + if (n.id == seg.notification.id && n.pkg.equals(seg.notification.pkg)) { + // just update that one to use this new data instead + mSegments.remove(i); + } + } + + 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 removeEntry(StatusBarNotification n) { + for (int i=mSegments.size()-1; i>=0; i--) { + Segment seg = mSegments.get(i); + if (n.id == seg.notification.id && n.pkg.equals(seg.notification.pkg)) { + mSegments.remove(i); + } + } + } + + 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..9108eee --- /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; + StatusBarService 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(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java new file mode 100644 index 0000000..b9e915a4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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.usb; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.storage.IMountService; +import android.os.Message; +import android.os.ServiceManager; +import android.os.storage.StorageEventListener; +import android.os.storage.StorageManager; +import android.os.storage.StorageResultCode; +import android.provider.Settings; +import android.util.Slog; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +public class StorageNotification extends StorageEventListener { + private static final String TAG = "StorageNotification"; + + private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true; + + /** + * Binder context for this service + */ + private Context mContext; + + /** + * The notification that is shown when a USB mass storage host + * is connected. + * <p> + * This is lazily created, so use {@link #setUsbStorageNotification()}. + */ + private Notification mUsbStorageNotification; + + /** + * The notification that is shown when the following media events occur: + * - Media is being checked + * - Media is blank (or unknown filesystem) + * - Media is corrupt + * - Media is safe to unmount + * - Media is missing + * <p> + * This is lazily created, so use {@link #setMediaStorageNotification()}. + */ + private Notification mMediaStorageNotification; + private boolean mUmsAvailable; + private StorageManager mStorageManager; + + public StorageNotification(Context context) { + mContext = context; + + mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); + final boolean connected = mStorageManager.isUsbMassStorageConnected(); + Slog.d(TAG, String.format( "Startup with UMS connection %s (media state %s)", mUmsAvailable, + Environment.getExternalStorageState())); + onUsbMassStorageConnectionChanged(connected); + } + + /* + * @override com.android.os.storage.StorageEventListener + */ + @Override + public void onUsbMassStorageConnectionChanged(boolean connected) { + mUmsAvailable = connected; + /* + * Even though we may have a UMS host connected, we the SD card + * may not be in a state for export. + */ + String st = Environment.getExternalStorageState(); + + Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st)); + + if (connected && (st.equals( + Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) { + /* + * No card or card being checked = don't display + */ + connected = false; + } + updateUsbMassStorageNotification(connected); + } + + /* + * @override com.android.os.storage.StorageEventListener + */ + @Override + public void onStorageStateChanged(String path, String oldState, String newState) { + Slog.i(TAG, String.format( + "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState)); + if (newState.equals(Environment.MEDIA_SHARED)) { + /* + * Storage is now shared. Modify the UMS notification + * for stopping UMS. + */ + Intent intent = new Intent(); + intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + setUsbStorageNotification( + com.android.internal.R.string.usb_storage_stop_notification_title, + com.android.internal.R.string.usb_storage_stop_notification_message, + com.android.internal.R.drawable.stat_sys_warning, false, true, pi); + } else if (newState.equals(Environment.MEDIA_CHECKING)) { + /* + * Storage is now checking. Update media notification and disable + * UMS notification. + */ + setMediaStorageNotification( + com.android.internal.R.string.ext_media_checking_notification_title, + com.android.internal.R.string.ext_media_checking_notification_message, + com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null); + updateUsbMassStorageNotification(false); + } else if (newState.equals(Environment.MEDIA_MOUNTED)) { + /* + * Storage is now mounted. Dismiss any media notifications, + * and enable UMS notification if connected. + */ + setMediaStorageNotification(0, 0, 0, false, false, null); + updateUsbMassStorageNotification(mUmsAvailable); + } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) { + /* + * Storage is now unmounted. We may have been unmounted + * because the user is enabling/disabling UMS, in which case we don't + * want to display the 'safe to unmount' notification. + */ + if (!mStorageManager.isUsbMassStorageEnabled()) { + if (oldState.equals(Environment.MEDIA_SHARED)) { + /* + * The unmount was due to UMS being enabled. Dismiss any + * media notifications, and enable UMS notification if connected + */ + setMediaStorageNotification(0, 0, 0, false, false, null); + updateUsbMassStorageNotification(mUmsAvailable); + } else { + /* + * Show safe to unmount media notification, and enable UMS + * notification if connected. + */ + setMediaStorageNotification( + com.android.internal.R.string.ext_media_safe_unmount_notification_title, + com.android.internal.R.string.ext_media_safe_unmount_notification_message, + com.android.internal.R.drawable.stat_notify_sdcard, true, true, null); + updateUsbMassStorageNotification(mUmsAvailable); + } + } else { + /* + * The unmount was due to UMS being enabled. Dismiss any + * media notifications, and disable the UMS notification + */ + setMediaStorageNotification(0, 0, 0, false, false, null); + updateUsbMassStorageNotification(false); + } + } else if (newState.equals(Environment.MEDIA_NOFS)) { + /* + * Storage has no filesystem. Show blank media notification, + * and enable UMS notification if connected. + */ + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + + setMediaStorageNotification( + com.android.internal.R.string.ext_media_nofs_notification_title, + com.android.internal.R.string.ext_media_nofs_notification_message, + com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); + updateUsbMassStorageNotification(mUmsAvailable); + } else if (newState.equals(Environment.MEDIA_UNMOUNTABLE)) { + /* + * Storage is corrupt. Show corrupt media notification, + * and enable UMS notification if connected. + */ + Intent intent = new Intent(); + intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + + setMediaStorageNotification( + com.android.internal.R.string.ext_media_unmountable_notification_title, + com.android.internal.R.string.ext_media_unmountable_notification_message, + com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); + updateUsbMassStorageNotification(mUmsAvailable); + } else if (newState.equals(Environment.MEDIA_REMOVED)) { + /* + * Storage has been removed. Show nomedia media notification, + * and disable UMS notification regardless of connection state. + */ + setMediaStorageNotification( + com.android.internal.R.string.ext_media_nomedia_notification_title, + com.android.internal.R.string.ext_media_nomedia_notification_message, + com.android.internal.R.drawable.stat_notify_sdcard_usb, + true, false, null); + updateUsbMassStorageNotification(false); + } else if (newState.equals(Environment.MEDIA_BAD_REMOVAL)) { + /* + * Storage has been removed unsafely. Show bad removal media notification, + * and disable UMS notification regardless of connection state. + */ + setMediaStorageNotification( + com.android.internal.R.string.ext_media_badremoval_notification_title, + com.android.internal.R.string.ext_media_badremoval_notification_message, + com.android.internal.R.drawable.stat_sys_warning, + true, true, null); + updateUsbMassStorageNotification(false); + } else { + Slog.w(TAG, String.format("Ignoring unknown state {%s}", newState)); + } + } + + /** + * Update the state of the USB mass storage notification + */ + void updateUsbMassStorageNotification(boolean available) { + + if (available) { + Intent intent = new Intent(); + intent.setClass(mContext, com.android.systemui.usb.UsbStorageActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); + setUsbStorageNotification( + com.android.internal.R.string.usb_storage_notification_title, + com.android.internal.R.string.usb_storage_notification_message, + com.android.internal.R.drawable.stat_sys_data_usb, + false, true, pi); + } else { + setUsbStorageNotification(0, 0, 0, false, false, null); + } + } + + /** + * Sets the USB storage notification. + */ + private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, + boolean sound, boolean visible, PendingIntent pi) { + + if (!visible && mUsbStorageNotification == null) { + return; + } + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + if (notificationManager == null) { + return; + } + + if (visible) { + Resources r = Resources.getSystem(); + CharSequence title = r.getText(titleId); + CharSequence message = r.getText(messageId); + + if (mUsbStorageNotification == null) { + mUsbStorageNotification = new Notification(); + mUsbStorageNotification.icon = icon; + mUsbStorageNotification.when = 0; + } + + if (sound) { + mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; + } else { + mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; + } + + mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; + + mUsbStorageNotification.tickerText = title; + if (pi == null) { + Intent intent = new Intent(); + pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); + } + + mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); + final boolean adbOn = 1 == Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.ADB_ENABLED, + 0); + + if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) { + // Pop up a full-screen alert to coach the user through enabling UMS. The average + // user has attached the device to USB either to charge the phone (in which case + // this is harmless) or transfer files, and in the latter case this alert saves + // several steps (as well as subtly indicates that you shouldn't mix UMS with other + // activities on the device). + // + // If ADB is enabled, however, we suppress this dialog (under the assumption that a + // developer (a) knows how to enable UMS, and (b) is probably using USB to install + // builds or use adb commands. + mUsbStorageNotification.fullScreenIntent = pi; + } + } + + final int notificationId = mUsbStorageNotification.icon; + if (visible) { + notificationManager.notify(notificationId, mUsbStorageNotification); + } else { + notificationManager.cancel(notificationId); + } + } + + private synchronized boolean getMediaStorageNotificationDismissable() { + if ((mMediaStorageNotification != null) && + ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) == + Notification.FLAG_AUTO_CANCEL)) + return true; + + return false; + } + + /** + * Sets the media storage notification. + */ + private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, + boolean dismissable, PendingIntent pi) { + + if (!visible && mMediaStorageNotification == null) { + return; + } + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + if (notificationManager == null) { + return; + } + + if (mMediaStorageNotification != null && visible) { + /* + * Dismiss the previous notification - we're about to + * re-use it. + */ + final int notificationId = mMediaStorageNotification.icon; + notificationManager.cancel(notificationId); + } + + if (visible) { + Resources r = Resources.getSystem(); + CharSequence title = r.getText(titleId); + CharSequence message = r.getText(messageId); + + if (mMediaStorageNotification == null) { + mMediaStorageNotification = new Notification(); + mMediaStorageNotification.when = 0; + } + + mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; + + if (dismissable) { + mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; + } else { + mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; + } + + mMediaStorageNotification.tickerText = title; + if (pi == null) { + Intent intent = new Intent(); + pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); + } + + mMediaStorageNotification.icon = icon; + mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); + } + + final int notificationId = mMediaStorageNotification.icon; + if (visible) { + notificationManager.notify(notificationId, mMediaStorageNotification); + } else { + notificationManager.cancel(notificationId); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java new file mode 100644 index 0000000..55d31ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbStorageActivity.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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.usb; + +import com.android.internal.R; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.DialogInterface.OnCancelListener; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.IBinder; +import android.os.storage.IMountService; +import android.os.storage.StorageManager; +import android.os.storage.StorageEventListener; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.widget.ImageView; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.view.View; +import android.view.Window; +import android.util.Log; + +import java.util.List; + +/** + * This activity is shown to the user for him/her to enable USB mass storage + * on-demand (that is, when the USB cable is connected). It uses the alert + * dialog style. It will be launched from a notification. + */ +public class UsbStorageActivity extends Activity + implements View.OnClickListener, OnCancelListener { + private static final String TAG = "UsbStorageActivity"; + + private Button mMountButton; + private Button mUnmountButton; + private ProgressBar mProgressBar; + private TextView mBanner; + private TextView mMessage; + private ImageView mIcon; + private StorageManager mStorageManager = null; + private static final int DLG_CONFIRM_KILL_STORAGE_USERS = 1; + private static final int DLG_ERROR_SHARING = 2; + static final boolean localLOGV = false; + + /** Used to detect when the USB cable is unplugged, so we can call finish() */ + private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) { + handleBatteryChanged(intent); + } + } + }; + + private StorageEventListener mStorageListener = new StorageEventListener() { + @Override + public void onStorageStateChanged(String path, String oldState, String newState) { + final boolean on = newState.equals(Environment.MEDIA_SHARED); + switchDisplay(on); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (mStorageManager == null) { + mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); + if (mStorageManager == null) { + Log.w(TAG, "Failed to get StorageManager"); + } + } + + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + setProgressBarIndeterminateVisibility(true); + + setTitle(getString(com.android.internal.R.string.usb_storage_activity_title)); + + setContentView(com.android.internal.R.layout.usb_storage_activity); + + mIcon = (ImageView) findViewById(com.android.internal.R.id.icon); + mBanner = (TextView) findViewById(com.android.internal.R.id.banner); + mMessage = (TextView) findViewById(com.android.internal.R.id.message); + + mMountButton = (Button) findViewById(com.android.internal.R.id.mount_button); + mMountButton.setOnClickListener(this); + mUnmountButton = (Button) findViewById(com.android.internal.R.id.unmount_button); + mUnmountButton.setOnClickListener(this); + mProgressBar = (ProgressBar) findViewById(com.android.internal.R.id.progress); + } + + private void switchDisplay(boolean usbStorageInUse) { + if (usbStorageInUse) { + mProgressBar.setVisibility(View.GONE); + mUnmountButton.setVisibility(View.VISIBLE); + mMountButton.setVisibility(View.GONE); + mIcon.setImageResource(com.android.internal.R.drawable.usb_android_connected); + mBanner.setText(com.android.internal.R.string.usb_storage_stop_title); + mMessage.setText(com.android.internal.R.string.usb_storage_stop_message); + } else { + mProgressBar.setVisibility(View.GONE); + mUnmountButton.setVisibility(View.GONE); + mMountButton.setVisibility(View.VISIBLE); + mIcon.setImageResource(com.android.internal.R.drawable.usb_android); + mBanner.setText(com.android.internal.R.string.usb_storage_title); + mMessage.setText(com.android.internal.R.string.usb_storage_message); + } + } + + @Override + protected void onResume() { + super.onResume(); + + mStorageManager.registerListener(mStorageListener); + registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + try { + switchDisplay(mStorageManager.isUsbMassStorageEnabled()); + } catch (Exception ex) { + Log.e(TAG, "Failed to read UMS enable state", ex); + } + } + + @Override + protected void onPause() { + super.onPause(); + + unregisterReceiver(mBatteryReceiver); + if (mStorageManager == null && mStorageListener != null) { + mStorageManager.unregisterListener(mStorageListener); + } + } + + private void handleBatteryChanged(Intent intent) { + int pluggedType = intent.getIntExtra("plugged", 0); + if (pluggedType == 0) { + // It was disconnected from the plug, so finish + finish(); + } + } + + private IMountService getMountService() { + IBinder service = ServiceManager.getService("mount"); + if (service != null) { + return IMountService.Stub.asInterface(service); + } + return null; + } + + @Override + public Dialog onCreateDialog(int id, Bundle args) { + switch (id) { + case DLG_CONFIRM_KILL_STORAGE_USERS: + return new AlertDialog.Builder(this) + .setTitle(R.string.dlg_confirm_kill_storage_users_title) + .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + switchUsbMassStorageAsync(true); + }}) + .setNegativeButton(R.string.cancel, null) + .setMessage(R.string.dlg_confirm_kill_storage_users_text) + .setOnCancelListener(this) + .create(); + case DLG_ERROR_SHARING: + return new AlertDialog.Builder(this) + .setTitle(R.string.dlg_error_title) + .setNeutralButton(R.string.dlg_ok, null) + .setMessage(R.string.usb_storage_error_message) + .setOnCancelListener(this) + .create(); + } + return null; + } + + private void showDialogInner(int id) { + removeDialog(id); + showDialog(id); + } + + private void switchUsbMassStorageAsync(boolean on) { + mUnmountButton.setVisibility(View.GONE); + mMountButton.setVisibility(View.GONE); + + mProgressBar.setVisibility(View.VISIBLE); + // will be hidden once USB mass storage kicks in (or fails) + + final boolean _on = on; + new Thread() { + public void run() { + if (_on) { + mStorageManager.enableUsbMassStorage(); + } else { + mStorageManager.disableUsbMassStorage(); + } + } + }.start(); + } + + private void checkStorageUsers() { + IMountService ims = getMountService(); + if (ims == null) { + // Display error dialog + showDialogInner(DLG_ERROR_SHARING); + } + String extStoragePath = Environment.getExternalStorageDirectory().toString(); + boolean showDialog = false; + try { + int[] stUsers = ims.getStorageUsers(extStoragePath); + if (stUsers != null && stUsers.length > 0) { + showDialog = true; + } else { + // List of applications on sdcard. + ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); + List<ApplicationInfo> infoList = am.getRunningExternalApplications(); + if (infoList != null && infoList.size() > 0) { + showDialog = true; + } + } + } catch (RemoteException e) { + // Display error dialog + showDialogInner(DLG_ERROR_SHARING); + } + if (showDialog) { + // Display dialog to user + showDialogInner(DLG_CONFIRM_KILL_STORAGE_USERS); + } else { + if (localLOGV) Log.i(TAG, "Enabling UMS"); + switchUsbMassStorageAsync(true); + } + } + + public void onClick(View v) { + if (v == mMountButton) { + // Check for list of storage users and display dialog if needed. + checkStorageUsers(); + } else if (v == mUnmountButton) { + if (localLOGV) Log.i(TAG, "Disabling UMS"); + switchUsbMassStorageAsync(false); + } + } + + public void onCancel(DialogInterface dialog) { + finish(); + } + +} |