blob: 0b161d66b659ee0acbed9d38682e2efed92a1511 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.status;
18
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019import android.app.AlertDialog;
20import android.bluetooth.BluetoothA2dp;
21import android.bluetooth.BluetoothDevice;
The Android Open Source Project10592532009-03-18 17:39:46 -070022import android.bluetooth.BluetoothError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.bluetooth.BluetoothHeadset;
24import android.bluetooth.BluetoothIntent;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.res.TypedArray;
31import android.graphics.PixelFormat;
32import android.graphics.drawable.Drawable;
33import android.media.AudioManager;
34import android.net.NetworkInfo;
35import android.net.wifi.WifiManager;
Dianne Hackborn4840e142009-03-24 22:40:29 -070036import android.os.Binder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.os.Handler;
38import android.os.IBinder;
39import android.os.Message;
Dianne Hackborn4840e142009-03-24 22:40:29 -070040import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.provider.Settings;
42import android.telephony.PhoneStateListener;
43import android.telephony.ServiceState;
44import android.telephony.TelephonyManager;
45import android.text.format.DateFormat;
46import android.util.Log;
47import android.view.View;
48import android.view.ViewGroup;
49import android.view.WindowManager;
50import android.view.WindowManagerImpl;
51import android.widget.ImageView;
52import android.widget.LinearLayout;
53import android.widget.TextView;
54
Wink Saville04e71b32009-04-02 11:00:54 -070055import com.android.internal.R;
56import com.android.internal.app.IBatteryStats;
57import com.android.internal.location.GpsLocationProvider;
58import com.android.internal.telephony.IccCard;
59import com.android.internal.telephony.TelephonyIntents;
60import com.android.internal.telephony.cdma.TtyIntent;
61import com.android.server.am.BatteryStatsService;
62
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063import java.util.Calendar;
64import java.util.TimeZone;
65
66/**
67 * This class contains all of the policy about which icons are installed in the status
68 * bar at boot time. In reality, it should go into the android.policy package, but
69 * putting it here is the first step from extracting it.
70 */
71public class StatusBarPolicy {
72 private static final String TAG = "StatusBarPolicy";
73
74 private static StatusBarPolicy sInstance;
75
76 // message codes for the handler
77 private static final int EVENT_DATA_CONN_STATE_CHANGED = 2;
78 private static final int EVENT_DATA_ACTIVITY = 3;
79 private static final int EVENT_BATTERY_CLOSE = 4;
80
81 // indices into mBatteryThresholds
82 private static final int BATTERY_THRESHOLD_CLOSE_WARNING = 0;
83 private static final int BATTERY_THRESHOLD_WARNING = 1;
84 private static final int BATTERY_THRESHOLD_EMPTY = 2;
85
Dianne Hackborn4840e142009-03-24 22:40:29 -070086 private final Context mContext;
87 private final StatusBarService mService;
88 private final Handler mHandler = new StatusBarHandler();
89 private final IBatteryStats mBatteryStats;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090
91 // clock
92 private Calendar mCalendar;
93 private IBinder mClockIcon;
94 private IconData mClockData;
95
96 // battery
97 private IBinder mBatteryIcon;
98 private IconData mBatteryData;
99 private boolean mBatteryFirst = true;
100 private boolean mBatteryPlugged;
101 private int mBatteryLevel;
102 private int mBatteryThreshold = 0; // index into mBatteryThresholds
103 private int[] mBatteryThresholds = new int[] { 20, 15, -1 };
104 private AlertDialog mLowBatteryDialog;
105 private TextView mBatteryLevelTextView;
106 private View mBatteryView;
107 private int mBatteryViewSequence;
108 private boolean mBatteryShowLowOnEndCall = false;
109 private static final boolean SHOW_LOW_BATTERY_WARNING = true;
110
111 // phone
112 private TelephonyManager mPhone;
113 private IBinder mPhoneIcon;
Wink Saville04e71b32009-04-02 11:00:54 -0700114
115 //***** Signal strength icons
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 private IconData mPhoneData;
Wink Saville04e71b32009-04-02 11:00:54 -0700117 //GSM/UMTS
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 private static final int[] sSignalImages = new int[] {
119 com.android.internal.R.drawable.stat_sys_signal_0,
120 com.android.internal.R.drawable.stat_sys_signal_1,
121 com.android.internal.R.drawable.stat_sys_signal_2,
122 com.android.internal.R.drawable.stat_sys_signal_3,
123 com.android.internal.R.drawable.stat_sys_signal_4
124 };
125 private static final int[] sSignalImages_r = new int[] {
126 com.android.internal.R.drawable.stat_sys_r_signal_0,
127 com.android.internal.R.drawable.stat_sys_r_signal_1,
128 com.android.internal.R.drawable.stat_sys_r_signal_2,
129 com.android.internal.R.drawable.stat_sys_r_signal_3,
130 com.android.internal.R.drawable.stat_sys_r_signal_4
131 };
Wink Saville04e71b32009-04-02 11:00:54 -0700132 //CDMA
133 private static final int[] sSignalImages_cdma = new int[] {
134 com.android.internal.R.drawable.stat_sys_signal_0_cdma,
135 com.android.internal.R.drawable.stat_sys_signal_1_cdma,
136 com.android.internal.R.drawable.stat_sys_signal_2_cdma,
137 com.android.internal.R.drawable.stat_sys_signal_3_cdma,
138 com.android.internal.R.drawable.stat_sys_signal_4_cdma
139 };
140 private static final int[] sSignalImages_r_cdma = new int[] {
141 com.android.internal.R.drawable.stat_sys_r_signal_0_cdma,
142 com.android.internal.R.drawable.stat_sys_r_signal_1_cdma,
143 com.android.internal.R.drawable.stat_sys_r_signal_2_cdma,
144 com.android.internal.R.drawable.stat_sys_r_signal_3_cdma,
145 com.android.internal.R.drawable.stat_sys_r_signal_4_cdma
146 };
147 private static final int[] sSignalImages_ra_cdma = new int[] {
148 com.android.internal.R.drawable.stat_sys_ra_signal_0_cdma,
149 com.android.internal.R.drawable.stat_sys_ra_signal_1_cdma,
150 com.android.internal.R.drawable.stat_sys_ra_signal_2_cdma,
151 com.android.internal.R.drawable.stat_sys_ra_signal_3_cdma,
152 com.android.internal.R.drawable.stat_sys_ra_signal_4_cdma
153 };
154
155 //***** Data connection icons
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 private int[] mDataIconList = sDataNetType_g;
Wink Saville04e71b32009-04-02 11:00:54 -0700157 //GSM/UMTS
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 private static final int[] sDataNetType_g = new int[] {
159 com.android.internal.R.drawable.stat_sys_data_connected_g,
160 com.android.internal.R.drawable.stat_sys_data_in_g,
161 com.android.internal.R.drawable.stat_sys_data_out_g,
162 com.android.internal.R.drawable.stat_sys_data_inandout_g,
163 };
164 private static final int[] sDataNetType_3g = new int[] {
165 com.android.internal.R.drawable.stat_sys_data_connected_3g,
166 com.android.internal.R.drawable.stat_sys_data_in_3g,
167 com.android.internal.R.drawable.stat_sys_data_out_3g,
168 com.android.internal.R.drawable.stat_sys_data_inandout_3g,
169 };
170 private static final int[] sDataNetType_e = new int[] {
171 com.android.internal.R.drawable.stat_sys_data_connected_e,
172 com.android.internal.R.drawable.stat_sys_data_in_e,
173 com.android.internal.R.drawable.stat_sys_data_out_e,
174 com.android.internal.R.drawable.stat_sys_data_inandout_e,
175 };
Wink Saville04e71b32009-04-02 11:00:54 -0700176 //CDMA
177 private static final int[] sDataNetType_evdo = new int[] {
178 com.android.internal.R.drawable.stat_sys_data_connected_evdo,
179 com.android.internal.R.drawable.stat_sys_data_in_evdo,
180 com.android.internal.R.drawable.stat_sys_data_out_evdo,
181 com.android.internal.R.drawable.stat_sys_data_inandout_evdo,
182 };
183 private static final int[] sDataNetType_1xrtt = new int[] {
184 com.android.internal.R.drawable.stat_sys_data_connected_1xrtt,
185 com.android.internal.R.drawable.stat_sys_data_in_1xrtt,
186 com.android.internal.R.drawable.stat_sys_data_out_1xrtt,
187 com.android.internal.R.drawable.stat_sys_data_inandout_1xrtt,
188 };
189
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 // Assume it's all good unless we hear otherwise. We don't always seem
191 // to get broadcasts that it *is* there.
Wink Saville04e71b32009-04-02 11:00:54 -0700192 IccCard.State mSimState = IccCard.State.READY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 int mPhoneState = TelephonyManager.CALL_STATE_IDLE;
194 int mDataState = TelephonyManager.DATA_DISCONNECTED;
195 int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
196 ServiceState mServiceState;
197 int mSignalAsu = -1;
198
199 // data connection
200 private IBinder mDataIcon;
201 private IconData mDataData;
202 private boolean mDataIconVisible;
203
204 // ringer volume
205 private IBinder mVolumeIcon;
206 private IconData mVolumeData;
207 private boolean mVolumeVisible;
Wink Saville04e71b32009-04-02 11:00:54 -0700208
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 // bluetooth device status
210 private IBinder mBluetoothIcon;
211 private IconData mBluetoothData;
212 private int mBluetoothHeadsetState;
213 private int mBluetoothA2dpState;
214 private boolean mBluetoothEnabled;
215
216 // wifi
217 private static final int[] sWifiSignalImages = new int[] {
218 com.android.internal.R.drawable.stat_sys_wifi_signal_1,
219 com.android.internal.R.drawable.stat_sys_wifi_signal_2,
220 com.android.internal.R.drawable.stat_sys_wifi_signal_3,
221 com.android.internal.R.drawable.stat_sys_wifi_signal_4,
222 };
223 private static final int sWifiTemporarilyNotConnectedImage =
224 com.android.internal.R.drawable.stat_sys_wifi_signal_0;
225
226 private int mLastWifiSignalLevel = -1;
227 private boolean mIsWifiConnected = false;
228 private IBinder mWifiIcon;
229 private IconData mWifiData;
230
231 // gps
232 private IBinder mGpsIcon;
233 private IconData mGpsEnabledIconData;
234 private IconData mGpsFixIconData;
235
236 // alarm clock
237 // Icon lit when clock is set
238 private IBinder mAlarmClockIcon;
239 private IconData mAlarmClockIconData;
240
241 // sync state
242 // If sync is active the SyncActive icon is displayed. If sync is not active but
243 // sync is failing the SyncFailing icon is displayed. Otherwise neither are displayed.
244 private IBinder mSyncActiveIcon;
245 private IBinder mSyncFailingIcon;
246
Wink Saville04e71b32009-04-02 11:00:54 -0700247 // TTY mode
248 // Icon lit when TTY mode is enabled
249 private IBinder mTTYModeIcon;
250 private IconData mTTYModeEnableIconData;
251
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
253 @Override
254 public void onReceive(Context context, Intent intent) {
255 String action = intent.getAction();
256 if (action.equals(Intent.ACTION_TIME_TICK)) {
257 updateClock();
258 }
259 else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
260 updateClock();
261 }
262 else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
263 updateClock();
264 }
265 else if (action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
266 String tz = intent.getStringExtra("time-zone");
267 mCalendar = Calendar.getInstance(TimeZone.getTimeZone(tz));
268 updateClock();
269 }
270 else if (action.equals(Intent.ACTION_ALARM_CHANGED)) {
271 updateAlarm(intent);
272 }
273 else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) {
274 updateSyncState(intent);
275 }
276 else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
277 updateBattery(intent);
278 }
The Android Open Source Project10592532009-03-18 17:39:46 -0700279 else if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION) ||
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION) ||
281 action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) {
282 updateBluetooth(intent);
283 }
284 else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) ||
285 action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION) ||
286 action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
287 updateWifi(intent);
288 }
289 else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) ||
290 action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION)) {
291 updateGps(intent);
292 }
293 else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) ||
294 action.equals(AudioManager.VIBRATE_SETTING_CHANGED_ACTION)) {
295 updateVolume();
296 }
297 else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
298 updateSimState(intent);
299 }
Wink Saville04e71b32009-04-02 11:00:54 -0700300 else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) {
301 updateTTY(intent);
302 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 }
304 };
305
306 private StatusBarPolicy(Context context, StatusBarService service) {
307 mContext = context;
308 mService = service;
Dianne Hackborn4840e142009-03-24 22:40:29 -0700309 mBatteryStats = BatteryStatsService.getService();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800310
311 // clock
312 mCalendar = Calendar.getInstance(TimeZone.getDefault());
313 mClockData = IconData.makeText("clock", "");
314 mClockIcon = service.addIcon(mClockData, null);
315 updateClock();
316
317 // battery
318 mBatteryData = IconData.makeIcon("battery",
319 null, com.android.internal.R.drawable.stat_sys_battery_unknown, 0, 0);
320 mBatteryIcon = service.addIcon(mBatteryData, null);
321
322 // phone_signal
323 mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
324 mPhoneData = IconData.makeIcon("phone_signal",
325 null, com.android.internal.R.drawable.stat_sys_signal_null, 0, 0);
326 mPhoneIcon = service.addIcon(mPhoneData, null);
327 // register for phone state notifications.
328 ((TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE))
329 .listen(mPhoneStateListener,
330 PhoneStateListener.LISTEN_SERVICE_STATE
331 | PhoneStateListener.LISTEN_SIGNAL_STRENGTH
332 | PhoneStateListener.LISTEN_CALL_STATE
333 | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
334 | PhoneStateListener.LISTEN_DATA_ACTIVITY);
335
336 // data_connection
337 mDataData = IconData.makeIcon("data_connection",
338 null, com.android.internal.R.drawable.stat_sys_data_connected_g, 0, 0);
339 mDataIcon = service.addIcon(mDataData, null);
340 service.setIconVisibility(mDataIcon, false);
341
342 // wifi
343 mWifiData = IconData.makeIcon("wifi", null, sWifiSignalImages[0], 0, 0);
344 mWifiIcon = service.addIcon(mWifiData, null);
345 service.setIconVisibility(mWifiIcon, false);
346 // wifi will get updated by the sticky intents
Wink Saville04e71b32009-04-02 11:00:54 -0700347
348 // TTY status
349 mTTYModeEnableIconData = IconData.makeIcon("tty",
350 null, com.android.internal.R.drawable.stat_sys_tty_mode, 0, 0);
351 mTTYModeIcon = service.addIcon(mTTYModeEnableIconData, null);
352 service.setIconVisibility(mTTYModeIcon, false);
353
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 // bluetooth status
355 mBluetoothData = IconData.makeIcon("bluetooth",
356 null, com.android.internal.R.drawable.stat_sys_data_bluetooth, 0, 0);
357 mBluetoothIcon = service.addIcon(mBluetoothData, null);
358 BluetoothDevice bluetooth =
359 (BluetoothDevice) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
360 if (bluetooth != null) {
361 mBluetoothEnabled = bluetooth.isEnabled();
362 } else {
363 mBluetoothEnabled = false;
364 }
365 mBluetoothA2dpState = BluetoothA2dp.STATE_DISCONNECTED;
366 mBluetoothHeadsetState = BluetoothHeadset.STATE_DISCONNECTED;
367 mService.setIconVisibility(mBluetoothIcon, mBluetoothEnabled);
368
369 // Gps status
370 mGpsEnabledIconData = IconData.makeIcon("gps",
371 null, com.android.internal.R.drawable.stat_sys_gps_acquiring_anim, 0, 0);
372 mGpsFixIconData = IconData.makeIcon("gps",
373 null, com.android.internal.R.drawable.stat_sys_gps_on, 0, 0);
374 mGpsIcon = service.addIcon(mGpsEnabledIconData, null);
375 service.setIconVisibility(mGpsIcon, false);
376
377 // Alarm clock
378 mAlarmClockIconData = IconData.makeIcon(
379 "alarm_clock",
380 null, com.android.internal.R.drawable.stat_notify_alarm, 0, 0);
381 mAlarmClockIcon = service.addIcon(mAlarmClockIconData, null);
382 service.setIconVisibility(mAlarmClockIcon, false);
383
384 // Sync state
385 mSyncActiveIcon = service.addIcon(IconData.makeIcon("sync_active",
386 null, R.drawable.stat_notify_sync_anim0, 0, 0), null);
387 mSyncFailingIcon = service.addIcon(IconData.makeIcon("sync_failing",
388 null, R.drawable.stat_notify_sync_error, 0, 0), null);
389 service.setIconVisibility(mSyncActiveIcon, false);
390 service.setIconVisibility(mSyncFailingIcon, false);
391
392 // volume
393 mVolumeData = IconData.makeIcon("volume",
394 null, com.android.internal.R.drawable.stat_sys_ringer_silent, 0, 0);
395 mVolumeIcon = service.addIcon(mVolumeData, null);
396 service.setIconVisibility(mVolumeIcon, false);
397 updateVolume();
398
399 IntentFilter filter = new IntentFilter();
400
401 // Register for Intent broadcasts for...
402 filter.addAction(Intent.ACTION_TIME_TICK);
403 filter.addAction(Intent.ACTION_TIME_CHANGED);
404 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
405 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
406 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
407 filter.addAction(Intent.ACTION_ALARM_CHANGED);
408 filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED);
409 filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
410 filter.addAction(AudioManager.VIBRATE_SETTING_CHANGED_ACTION);
The Android Open Source Project10592532009-03-18 17:39:46 -0700411 filter.addAction(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
413 filter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
414 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
415 filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
416 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
417 filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
418 filter.addAction(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION);
419 filter.addAction(GpsLocationProvider.GPS_FIX_CHANGE_ACTION);
420 filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
Wink Saville04e71b32009-04-02 11:00:54 -0700421 filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
423 }
424
425 public static void installIcons(Context context, StatusBarService service) {
426 sInstance = new StatusBarPolicy(context, service);
427 }
428
429 private final void updateClock() {
430 mCalendar.setTimeInMillis(System.currentTimeMillis());
431 mClockData.text = DateFormat.getTimeFormat(mContext)
432 .format(mCalendar.getTime());
433 mService.updateIcon(mClockIcon, mClockData, null);
434 }
435
436 private final void updateAlarm(Intent intent) {
437 boolean alarmSet = intent.getBooleanExtra("alarmSet", false);
438 mService.setIconVisibility(mAlarmClockIcon, alarmSet);
439 }
440
441 private final void updateSyncState(Intent intent) {
442 boolean isActive = intent.getBooleanExtra("active", false);
443 boolean isFailing = intent.getBooleanExtra("failing", false);
444 mService.setIconVisibility(mSyncActiveIcon, isActive);
445 // Don't display sync failing icon: BUG 1297963 Set sync error timeout to "never"
446 //mService.setIconVisibility(mSyncFailingIcon, isFailing && !isActive);
447 }
448
449 private void pickNextBatteryLevel(int level) {
450 final int N = mBatteryThresholds.length;
451 for (int i=0; i<N; i++) {
452 if (level >= mBatteryThresholds[i]) {
453 mBatteryThreshold = i;
454 break;
455 }
456 }
457 if (mBatteryThreshold >= N) {
458 mBatteryThreshold = N-1;
459 }
460 }
461
462 private final void updateBattery(Intent intent) {
463 mBatteryData.iconId = intent.getIntExtra("icon-small", 0);
464 mBatteryData.iconLevel = intent.getIntExtra("level", 0);
465 mService.updateIcon(mBatteryIcon, mBatteryData, null);
466
467 boolean plugged = intent.getIntExtra("plugged", 0) != 0;
468 int level = intent.getIntExtra("level", -1);
469 if (false) {
470 Log.d(TAG, "updateBattery level=" + level
471 + " plugged=" + plugged
472 + " mBatteryPlugged=" + mBatteryPlugged
473 + " mBatteryLevel=" + mBatteryLevel
474 + " mBatteryThreshold=" + mBatteryThreshold
475 + " mBatteryFirst=" + mBatteryFirst);
476 }
477
478 boolean oldPlugged = mBatteryPlugged;
479 int oldThreshold = mBatteryThreshold;
480 pickNextBatteryLevel(level);
481
482 mBatteryPlugged = plugged;
483 mBatteryLevel = level;
484
485 if (mBatteryFirst) {
486 mBatteryFirst = false;
487 }
488 /*
489 * No longer showing the battery view because it draws attention away
490 * from the USB storage notification. We could still show it when
491 * connected to a brick, but that could lead to the user into thinking
492 * the device does not charge when plugged into USB (since he/she would
493 * not see the same battery screen on USB as he sees on brick).
494 */
495 /* else {
496 if (plugged && !oldPlugged) {
497 showBatteryView();
498 }
499 }
500 */
501 if (false) {
502 Log.d(TAG, "plugged=" + plugged + " oldPlugged=" + oldPlugged + " level=" + level
503 + " mBatteryThreshold=" + mBatteryThreshold + " oldThreshold=" + oldThreshold);
504 }
505 if (!plugged
506 && ((oldPlugged && level < mBatteryThresholds[BATTERY_THRESHOLD_WARNING])
507 || (mBatteryThreshold > oldThreshold
508 && mBatteryThreshold > BATTERY_THRESHOLD_WARNING))) {
509 // Broadcast the low battery warning
510 mContext.sendBroadcast(new Intent(Intent.ACTION_BATTERY_LOW));
511
512 if (SHOW_LOW_BATTERY_WARNING) {
513 if (false) {
514 Log.d(TAG, "mPhoneState=" + mPhoneState
515 + " mLowBatteryDialog=" + mLowBatteryDialog
516 + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall);
517 }
518
519 if (mPhoneState == TelephonyManager.CALL_STATE_IDLE) {
520 showLowBatteryWarning();
521 } else {
522 mBatteryShowLowOnEndCall = true;
523 }
524 }
525 } else if (mBatteryThreshold == BATTERY_THRESHOLD_CLOSE_WARNING) {
526 if (SHOW_LOW_BATTERY_WARNING) {
527 if (mLowBatteryDialog != null) {
528 mLowBatteryDialog.dismiss();
529 mBatteryShowLowOnEndCall = false;
530 }
531 }
532 }
533 }
534
535 private void showBatteryView() {
536 closeLastBatteryView();
537 if (mLowBatteryDialog != null) {
538 mLowBatteryDialog.dismiss();
539 }
540
541 int level = mBatteryLevel;
542
543 View v = View.inflate(mContext, com.android.internal.R.layout.battery_status, null);
544 mBatteryView = v;
545 int pixelFormat = PixelFormat.TRANSLUCENT;
546 Drawable bg = v.getBackground();
547 if (bg != null) {
548 pixelFormat = bg.getOpacity();
549 }
550
551 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
552 ViewGroup.LayoutParams.WRAP_CONTENT,
553 ViewGroup.LayoutParams.WRAP_CONTENT,
554 WindowManager.LayoutParams.TYPE_TOAST,
555 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
556 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
557 | WindowManager.LayoutParams.FLAG_BLUR_BEHIND
558 | WindowManager.LayoutParams.FLAG_DIM_BEHIND,
559 pixelFormat);
560
561 // Get the dim amount from the theme
562 TypedArray a = mContext.obtainStyledAttributes(
563 com.android.internal.R.styleable.Theme);
564 lp.dimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f);
565 a.recycle();
Wink Saville04e71b32009-04-02 11:00:54 -0700566
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 lp.setTitle("Battery");
568
569 TextView levelTextView = (TextView)v.findViewById(com.android.internal.R.id.level_percent);
570 levelTextView.setText(mContext.getString(
571 com.android.internal.R.string.battery_status_text_percent_format, level));
572
573 setBatteryLevel(v, com.android.internal.R.id.spacer, 100-level, 0, 0);
574 setBatteryLevel(v, com.android.internal.R.id.level, level,
575 com.android.internal.R.drawable.battery_charge_fill, level);
576
577 WindowManagerImpl.getDefault().addView(v, lp);
578
579 scheduleCloseBatteryView();
580 }
581
582 private void setBatteryLevel(View parent, int id, int height, int background, int level) {
583 ImageView v = (ImageView)parent.findViewById(id);
584 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)v.getLayoutParams();
585 lp.weight = height;
586 if (background != 0) {
587 v.setBackgroundResource(background);
588 Drawable bkg = v.getBackground();
589 bkg.setLevel(level);
590 }
591 }
592
593 private void showLowBatteryWarning() {
594 closeLastBatteryView();
595
596 int level = mBatteryThresholds[mBatteryThreshold > 1 ? mBatteryThreshold - 1 : 0];
597 CharSequence levelText = mContext.getString(
598 com.android.internal.R.string.battery_low_percent_format, level);
599
600 if (mBatteryLevelTextView != null) {
601 mBatteryLevelTextView.setText(levelText);
602 } else {
603 View v = View.inflate(mContext, com.android.internal.R.layout.battery_low, null);
604 mBatteryLevelTextView=(TextView)v.findViewById(com.android.internal.R.id.level_percent);
605
606 mBatteryLevelTextView.setText(levelText);
607
608 AlertDialog.Builder b = new AlertDialog.Builder(mContext);
609 b.setCancelable(true);
610 b.setTitle(com.android.internal.R.string.battery_low_title);
611 b.setView(v);
612 b.setIcon(android.R.drawable.ic_dialog_alert);
613 b.setPositiveButton(android.R.string.ok, null);
614
615 AlertDialog d = b.create();
616 d.setOnDismissListener(mLowBatteryListener);
617 d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
618 d.show();
619 mLowBatteryDialog = d;
620 }
621 }
622
623 private final void updateCallState(int state) {
624 mPhoneState = state;
625 if (false) {
626 Log.d(TAG, "mPhoneState=" + mPhoneState
627 + " mLowBatteryDialog=" + mLowBatteryDialog
628 + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall);
629 }
630 if (mPhoneState == TelephonyManager.CALL_STATE_IDLE) {
631 if (mBatteryShowLowOnEndCall) {
632 if (!mBatteryPlugged) {
633 showLowBatteryWarning();
634 }
635 mBatteryShowLowOnEndCall = false;
636 }
637 } else {
638 if (mLowBatteryDialog != null) {
639 mLowBatteryDialog.dismiss();
640 mBatteryShowLowOnEndCall = true;
641 }
642 }
643 }
644
645 private DialogInterface.OnDismissListener mLowBatteryListener
646 = new DialogInterface.OnDismissListener() {
647 public void onDismiss(DialogInterface dialog) {
648 mLowBatteryDialog = null;
649 mBatteryLevelTextView = null;
650 }
651 };
652
653 private void scheduleCloseBatteryView() {
654 Message m = mHandler.obtainMessage(EVENT_BATTERY_CLOSE);
655 m.arg1 = (++mBatteryViewSequence);
656 mHandler.sendMessageDelayed(m, 3000);
657 }
658
659 private void closeLastBatteryView() {
660 if (mBatteryView != null) {
661 //mBatteryView.debug();
662 WindowManagerImpl.getDefault().removeView(mBatteryView);
663 mBatteryView = null;
664 }
665 }
666
667 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
668 @Override
669 public void onSignalStrengthChanged(int asu) {
670 mSignalAsu = asu;
671 updateSignalStrength();
672 }
673
674 @Override
675 public void onServiceStateChanged(ServiceState state) {
676 mServiceState = state;
677 updateSignalStrength();
678 updateDataIcon();
679 }
680
681 @Override
682 public void onCallStateChanged(int state, String incomingNumber) {
683 updateCallState(state);
684 }
685
686 @Override
687 public void onDataConnectionStateChanged(int state) {
688 mDataState = state;
689 updateDataNetType();
690 updateDataIcon();
691 }
692
693 @Override
694 public void onDataActivity(int direction) {
695 mDataActivity = direction;
696 updateDataIcon();
697 }
698 };
699
700
701 private final void updateSimState(Intent intent) {
Wink Saville04e71b32009-04-02 11:00:54 -0700702 String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE);
703 if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
704 mSimState = IccCard.State.ABSENT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705 }
Wink Saville04e71b32009-04-02 11:00:54 -0700706 else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
707 mSimState = IccCard.State.READY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 }
Wink Saville04e71b32009-04-02 11:00:54 -0700709 else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
710 final String lockedReason = intent.getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON);
711 if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
712 mSimState = IccCard.State.PIN_REQUIRED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 }
Wink Saville04e71b32009-04-02 11:00:54 -0700714 else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
715 mSimState = IccCard.State.PUK_REQUIRED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716 }
717 else {
Wink Saville04e71b32009-04-02 11:00:54 -0700718 mSimState = IccCard.State.NETWORK_LOCKED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800719 }
720 } else {
Wink Saville04e71b32009-04-02 11:00:54 -0700721 mSimState = IccCard.State.UNKNOWN;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722 }
723 updateDataIcon();
724 }
725
726 private final void updateSignalStrength() {
727 int asu = mSignalAsu;
728 ServiceState ss = mServiceState;
729
730 boolean hasService = true;
731
732 if (ss != null) {
733 int state = ss.getState();
734 switch (state) {
735 case ServiceState.STATE_OUT_OF_SERVICE:
736 case ServiceState.STATE_POWER_OFF:
737 hasService = false;
738 break;
739 }
740 } else {
741 hasService = false;
742 }
743
744 if (!hasService) {
745 //Log.d(TAG, "updateSignalStrength: no service");
746 if (Settings.System.getInt(mContext.getContentResolver(),
747 Settings.System.AIRPLANE_MODE_ON, 0) == 1) {
748 mPhoneData.iconId = com.android.internal.R.drawable.stat_sys_signal_flightmode;
749 } else {
750 mPhoneData.iconId = com.android.internal.R.drawable.stat_sys_signal_null;
751 }
752 mService.updateIcon(mPhoneIcon, mPhoneData, null);
753 return;
754 }
755
756 // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
757 // asu = 0 (-113dB or less) is very weak
758 // signal, its better to show 0 bars to the user in such cases.
759 // asu = 99 is a special case, where the signal strength is unknown.
760 if (asu <= 0 || asu == 99) asu = 0;
761 else if (asu >= 16) asu = 4;
762 else if (asu >= 8) asu = 3;
763 else if (asu >= 4) asu = 2;
764 else asu = 1;
765
766 int[] iconList;
Wink Saville04e71b32009-04-02 11:00:54 -0700767 if (mPhone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
768 switch(ss.getExtendedCdmaRoaming()) {
769 case ServiceState.REGISTRATION_STATE_ROAMING:
770 iconList = this.sSignalImages_r_cdma;
771 break;
772 case ServiceState.REGISTRATION_STATE_ROAMING_AFFILIATE:
773 iconList = this.sSignalImages_ra_cdma;
774 break;
775 default:
776 iconList = this.sSignalImages_cdma;
777 break;
778 }
779 } else if (mPhone.isNetworkRoaming()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800780 iconList = sSignalImages_r;
781 } else {
782 iconList = sSignalImages;
783 }
Wink Saville04e71b32009-04-02 11:00:54 -0700784
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800785 mPhoneData.iconId = iconList[asu];
786 mService.updateIcon(mPhoneIcon, mPhoneData, null);
787 }
788
789 private final void updateDataNetType() {
Wink Saville04e71b32009-04-02 11:00:54 -0700790 int net = mPhone.getNetworkType();
791 ServiceState ss = this.mServiceState;
792
793 switch (net) {
794
795 case TelephonyManager.NETWORK_TYPE_EDGE:
796 mDataIconList = sDataNetType_e;
797 break;
798 case TelephonyManager.NETWORK_TYPE_UMTS:
799 mDataIconList = sDataNetType_3g;
800 break;
801 case TelephonyManager.NETWORK_TYPE_CDMA:
802 // display 1xRTT for IS95A/B
803 mDataIconList = this.sDataNetType_1xrtt;
804 break;
805 case TelephonyManager.NETWORK_TYPE_1xRTT:
806 mDataIconList = this.sDataNetType_1xrtt;
807 break;
808 case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through
809 case TelephonyManager.NETWORK_TYPE_EVDO_A:
810 mDataIconList = sDataNetType_evdo;
811 break;
812 default:
813 mDataIconList = sDataNetType_g;
814 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815 }
816 }
817
818 private final void updateDataIcon() {
819 int iconId;
820 boolean visible = true;
821
Wink Saville04e71b32009-04-02 11:00:54 -0700822 if (mSimState == IccCard.State.READY || mSimState == IccCard.State.UNKNOWN) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800823 int data = mDataState;
Wink Saville04e71b32009-04-02 11:00:54 -0700824
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 int[] list = mDataIconList;
826
827 ServiceState ss = mServiceState;
828
829 boolean hasService = false;
830
831 if (ss != null) {
832 hasService = (ss.getState() == ServiceState.STATE_IN_SERVICE);
833 }
834
835 if (hasService && data == TelephonyManager.DATA_CONNECTED) {
836 switch (mDataActivity) {
837 case TelephonyManager.DATA_ACTIVITY_IN:
838 iconId = list[1];
839 break;
840 case TelephonyManager.DATA_ACTIVITY_OUT:
841 iconId = list[2];
842 break;
843 case TelephonyManager.DATA_ACTIVITY_INOUT:
844 iconId = list[3];
845 break;
846 default:
847 iconId = list[0];
848 break;
849 }
850 mDataData.iconId = iconId;
851 mService.updateIcon(mDataIcon, mDataData, null);
852 } else {
853 visible = false;
854 }
855 } else {
856 mDataData.iconId = com.android.internal.R.drawable.stat_sys_no_sim;
857 mService.updateIcon(mDataIcon, mDataData, null);
858 }
Dianne Hackborn4840e142009-03-24 22:40:29 -0700859 long ident = Binder.clearCallingIdentity();
860 try {
Wink Saville04e71b32009-04-02 11:00:54 -0700861 mBatteryStats.notePhoneDataConnectionState(mPhone.getNetworkType(), visible);
Dianne Hackborn4840e142009-03-24 22:40:29 -0700862 } catch (RemoteException e) {
863 } finally {
864 Binder.restoreCallingIdentity(ident);
865 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 if (mDataIconVisible != visible) {
867 mService.setIconVisibility(mDataIcon, visible);
868 mDataIconVisible = visible;
869 }
870 }
871
872 private final void updateVolume() {
873 AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
874 final int ringerMode = audioManager.getRingerMode();
875 final boolean visible = ringerMode == AudioManager.RINGER_MODE_SILENT ||
876 ringerMode == AudioManager.RINGER_MODE_VIBRATE;
877 final int iconId = audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)
878 ? com.android.internal.R.drawable.stat_sys_ringer_vibrate
879 : com.android.internal.R.drawable.stat_sys_ringer_silent;
880
881 if (visible) {
882 mVolumeData.iconId = iconId;
883 mService.updateIcon(mVolumeIcon, mVolumeData, null);
884 }
885 if (visible != mVolumeVisible) {
886 mService.setIconVisibility(mVolumeIcon, visible);
887 mVolumeVisible = visible;
888 }
889 }
890
891 private final void updateBluetooth(Intent intent) {
892 int iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth;
893
894 String action = intent.getAction();
The Android Open Source Project10592532009-03-18 17:39:46 -0700895 if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) {
896 int state = intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE,
897 BluetoothError.ERROR);
898 mBluetoothEnabled = state == BluetoothDevice.BLUETOOTH_STATE_ON;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
900 mBluetoothHeadsetState = intent.getIntExtra(BluetoothIntent.HEADSET_STATE,
901 BluetoothHeadset.STATE_ERROR);
902 } else if (action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) {
903 mBluetoothA2dpState = intent.getIntExtra(BluetoothA2dp.SINK_STATE,
904 BluetoothA2dp.STATE_DISCONNECTED);
905 } else {
906 return;
907 }
908
909 if (mBluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED ||
910 mBluetoothA2dpState == BluetoothA2dp.STATE_CONNECTED ||
911 mBluetoothA2dpState == BluetoothA2dp.STATE_PLAYING) {
912 iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth_connected;
913 }
914
915 mBluetoothData.iconId = iconId;
916 mService.updateIcon(mBluetoothIcon, mBluetoothData, null);
917 mService.setIconVisibility(mBluetoothIcon, mBluetoothEnabled);
918 }
919
920 private final void updateWifi(Intent intent) {
921 final String action = intent.getAction();
922 if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
923
924 final boolean enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
925 WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
926
927 if (!enabled) {
928 // If disabled, hide the icon. (We show icon when connected.)
929 mService.setIconVisibility(mWifiIcon, false);
930 }
931
932 } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
933 final boolean enabled = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED,
934 false);
935 if (!enabled) {
936 mService.setIconVisibility(mWifiIcon, false);
937 }
938 } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
939
940 final NetworkInfo networkInfo = (NetworkInfo)
941 intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
942
943 int iconId;
944 if (networkInfo != null && networkInfo.isConnected()) {
945 mIsWifiConnected = true;
946 if (mLastWifiSignalLevel == -1) {
947 iconId = sWifiSignalImages[0];
948 } else {
949 iconId = sWifiSignalImages[mLastWifiSignalLevel];
950 }
Wink Saville04e71b32009-04-02 11:00:54 -0700951
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800952 // Show the icon since wi-fi is connected
953 mService.setIconVisibility(mWifiIcon, true);
Wink Saville04e71b32009-04-02 11:00:54 -0700954
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 } else {
956 mLastWifiSignalLevel = -1;
957 mIsWifiConnected = false;
958 iconId = sWifiSignalImages[0];
959
960 // Hide the icon since we're not connected
961 mService.setIconVisibility(mWifiIcon, false);
962 }
963
964 mWifiData.iconId = iconId;
965 mService.updateIcon(mWifiIcon, mWifiData, null);
966 } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
967 final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
968 int newSignalLevel = WifiManager.calculateSignalLevel(newRssi,
969 sWifiSignalImages.length);
970 if (newSignalLevel != mLastWifiSignalLevel) {
971 mLastWifiSignalLevel = newSignalLevel;
972 if (mIsWifiConnected) {
973 mWifiData.iconId = sWifiSignalImages[newSignalLevel];
974 } else {
975 mWifiData.iconId = sWifiTemporarilyNotConnectedImage;
976 }
977 mService.updateIcon(mWifiIcon, mWifiData, null);
978 }
979 }
980 }
981
982 private final void updateGps(Intent intent) {
983 final String action = intent.getAction();
984 final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED, false);
985
986 if (action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION) && enabled) {
987 // GPS is getting fixes
988 mService.updateIcon(mGpsIcon, mGpsFixIconData, null);
989 mService.setIconVisibility(mGpsIcon, true);
990 } else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) && !enabled) {
991 // GPS is off
992 mService.setIconVisibility(mGpsIcon, false);
993 } else {
994 // GPS is on, but not receiving fixes
995 mService.updateIcon(mGpsIcon, mGpsEnabledIconData, null);
996 mService.setIconVisibility(mGpsIcon, true);
997 }
998 }
999
Wink Saville04e71b32009-04-02 11:00:54 -07001000 private final void updateTTY(Intent intent) {
1001 final String action = intent.getAction();
1002 final boolean enabled = intent.getBooleanExtra(TtyIntent.TTY_ENABLED, false);
1003
1004 Log.i(TAG, "updateTTY: enabled: " + enabled);
1005
1006 if (enabled) {
1007 // TTY is on
1008 Log.i(TAG, "updateTTY: set TTY on");
1009 mService.updateIcon(mTTYModeIcon, mTTYModeEnableIconData, null);
1010 mService.setIconVisibility(mTTYModeIcon, true);
1011 } else {
1012 // TTY is off
1013 Log.i(TAG, "updateTTY: set TTY off");
1014 mService.setIconVisibility(mTTYModeIcon, false);
1015 }
1016 }
1017
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018 private class StatusBarHandler extends Handler {
1019 @Override
1020 public void handleMessage(Message msg) {
1021 switch (msg.what) {
1022 case EVENT_BATTERY_CLOSE:
1023 if (msg.arg1 == mBatteryViewSequence) {
1024 closeLastBatteryView();
1025 }
1026 break;
1027 }
1028 }
1029 }
1030}
Wink Saville04e71b32009-04-02 11:00:54 -07001031
1032
1033