From 291e1a50b41a6ae47eddb1713dc99d22f96d0c9f Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- src/com/android/alarmclock/AlarmAlert.java | 276 ++++++++ src/com/android/alarmclock/AlarmAlertWakeLock.java | 55 ++ src/com/android/alarmclock/AlarmClock.java | 292 ++++++++ src/com/android/alarmclock/AlarmInitReceiver.java | 44 ++ src/com/android/alarmclock/AlarmPreference.java | 54 ++ src/com/android/alarmclock/AlarmProvider.java | 237 +++++++ src/com/android/alarmclock/AlarmReceiver.java | 57 ++ src/com/android/alarmclock/Alarms.java | 753 +++++++++++++++++++++ src/com/android/alarmclock/ClockPicker.java | 119 ++++ src/com/android/alarmclock/DigitalClock.java | 209 ++++++ src/com/android/alarmclock/Log.java | 39 ++ src/com/android/alarmclock/RepeatPreference.java | 72 ++ src/com/android/alarmclock/SetAlarm.java | 369 ++++++++++ src/com/android/alarmclock/ToastMaster.java | 41 ++ 14 files changed, 2617 insertions(+) create mode 100644 src/com/android/alarmclock/AlarmAlert.java create mode 100644 src/com/android/alarmclock/AlarmAlertWakeLock.java create mode 100644 src/com/android/alarmclock/AlarmClock.java create mode 100644 src/com/android/alarmclock/AlarmInitReceiver.java create mode 100644 src/com/android/alarmclock/AlarmPreference.java create mode 100644 src/com/android/alarmclock/AlarmProvider.java create mode 100644 src/com/android/alarmclock/AlarmReceiver.java create mode 100644 src/com/android/alarmclock/Alarms.java create mode 100644 src/com/android/alarmclock/ClockPicker.java create mode 100644 src/com/android/alarmclock/DigitalClock.java create mode 100644 src/com/android/alarmclock/Log.java create mode 100644 src/com/android/alarmclock/RepeatPreference.java create mode 100644 src/com/android/alarmclock/SetAlarm.java create mode 100644 src/com/android/alarmclock/ToastMaster.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java new file mode 100644 index 000000000..31da6762f --- /dev/null +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.view.ViewGroup; +import android.view.LayoutInflater; +import android.widget.Button; +import android.widget.Toast; + +import java.util.Calendar; + +/** + * Alarm Clock alarm alert: pops visible indicator and plays alarm + * tone + */ +public class AlarmAlert extends Activity implements Alarms.AlarmSettings { + + private static long[] sVibratePattern = new long[] { 500, 500 }; + + private NotificationManager mNotificationManager; + + private final static int AUDIO_ALERT_NOTIFICATION_ID = 0; + /** Play alarm up to 5 minutes before silencing */ + private final static int ALARM_TIMEOUT_SECONDS = 300; + private final static int SNOOZE_MINUTES = 10; + + private KeyguardManager mKeyguardManager; + private KeyguardManager.KeyguardLock mKeyguardLock = null; + private Handler mTimeout; + private Button mSnoozeButton; + + private int mAlarmId; + private Alarms.DaysOfWeek mDaysOfWeek; + private String mAlert; + private boolean mVibrate; + private boolean mSnoozed; + private boolean mCleanupCalled = false; + + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + if (Log.LOGV) Log.v("AlarmAlert.reportAlarm: " + idx + " " + hour + + " " + minutes + " dow " + daysOfWeek); + mAlert = alert; + mDaysOfWeek = daysOfWeek; + mVibrate = vibrate; + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.alarm_alert); + + mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + /* set clock face */ + LayoutInflater mFactory = LayoutInflater.from(this); + SharedPreferences settings = getSharedPreferences(AlarmClock.PREFERENCES, 0); + int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); + if (face < 0 || face >= AlarmClock.CLOCKS.length) face = 0; + View clockLayout = (View)mFactory.inflate(AlarmClock.CLOCKS[face], null); + ViewGroup clockView = (ViewGroup)findViewById(R.id.clockView); + clockView.addView(clockLayout); + if (clockLayout instanceof DigitalClock) { + ((DigitalClock)clockLayout).setAnimate(); + } + + playAlert(getIntent()); + + /* allow next alarm to trigger while this activity is + active */ + Alarms.disableSnoozeAlert(AlarmAlert.this); + Alarms.disableAlert(AlarmAlert.this, mAlarmId); + Alarms.setNextAlert(this); + + /* snooze behavior: pop a snooze confirmation view, kick alarm + manager. */ + mSnoozeButton = (Button) findViewById(R.id.snooze); + mSnoozeButton.requestFocus(); + mSnoozeButton.setOnClickListener(new Button.OnClickListener() { + public void onClick(View v) { + /* If next alarm is set for sooner than the snooze interval, + don't snooze: instead toast user that snooze will not be set */ + final long snoozeTarget = System.currentTimeMillis() + 1000 * 60 * SNOOZE_MINUTES; + long nextAlarm = Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); + if (nextAlarm < snoozeTarget) { + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(nextAlarm); + Toast.makeText(AlarmAlert.this, + getString(R.string.alarm_alert_snooze_not_set, + Alarms.formatTime(AlarmAlert.this, c)), + Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(AlarmAlert.this, + getString(R.string.alarm_alert_snooze_set, + SNOOZE_MINUTES), + Toast.LENGTH_LONG).show(); + + Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTarget); + Alarms.setNextAlert(AlarmAlert.this); + mSnoozed = true; + } + disableKiller(); + cleanupAlarm(); + releaseLocks(); + finish(); + } + }); + + /* dismiss button: close notification */ + findViewById(R.id.dismiss).setOnClickListener(new Button.OnClickListener() { + public void onClick(View v) { + disableKiller(); + cleanupAlarm(); + releaseLocks(); + finish(); + } + }); + } + + /** + * this is called when a second alarm is triggered while a + * previous alert window is still active. + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); + mSnoozeButton.setEnabled(true); + disableKeyguard(); + cleanupAlarm(); + mCleanupCalled = false; + disableKiller(); + playAlert(intent); + Alarms.setNextAlert(this); + setIntent(intent); + } + + @Override + protected void onResume() { + super.onResume(); + if (Log.LOGV) Log.v("AlarmAlert.onResume()"); + disableKeyguard(); + } + + @Override + protected void onStop() { + super.onStop(); + if (Log.LOGV) Log.v("AlarmAlert.onStop()"); + disableKiller(); + cleanupAlarm(); + releaseLocks(); + } + + /** + * kicks off audio/vibe alert + */ + private void playAlert(Intent intent) { + mAlarmId = intent.getIntExtra(Alarms.ID, 0); + if (Log.LOGV) Log.v("playAlert() " + mAlarmId); + + /* load audio alert */ + ContentResolver cr = getContentResolver(); + /* this will call reportAlarm() callback */ + Alarms.getAlarm(cr, this, mAlarmId); + + /* play audio alert */ + if (mAlert == null) { + Log.e("Unable to play alarm: no audio file available"); + } else { + Notification audio = new Notification(); + audio.sound = Uri.parse(mAlert); + audio.audioStreamType = AudioManager.STREAM_ALARM; + audio.flags |= Notification.FLAG_INSISTENT; + if (mVibrate) audio.vibrate = sVibratePattern; + mNotificationManager.notify(AUDIO_ALERT_NOTIFICATION_ID, audio); + } + enableKiller(); + } + + /** + * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm + * won't run all day. + * + * This just cancels the audio, but leaves the notification + * popped, so the user will know that the alarm tripped. + */ + private void enableKiller() { + mTimeout = new Handler(); + mTimeout.postDelayed(new Runnable() { + public void run() { + if (Log.LOGV) Log.v("*********** Alarm killer triggered *************"); + /* don't allow snooze */ + mSnoozeButton.setEnabled(false); + cleanupAlarm(); + releaseLocks(); + }}, 1000 * ALARM_TIMEOUT_SECONDS); + } + + private void disableKiller() { + if (mTimeout != null) { + mTimeout.removeCallbacksAndMessages(null); + mTimeout = null; + } + } + + private synchronized void enableKeyguard() { + if (mKeyguardLock != null) { + mKeyguardLock.reenableKeyguard(); + mKeyguardLock = null; + } + } + + private synchronized void disableKeyguard() { + if (mKeyguardLock == null) { + mKeyguardLock = mKeyguardManager.newKeyguardLock(Log.LOGTAG); + mKeyguardLock.disableKeyguard(); + } + } + + /** + * release wake and keyguard locks + */ + private synchronized void releaseLocks() { + AlarmAlertWakeLock.release(); + enableKeyguard(); + } + + /** + * Stops alarm audio and disables alarm if it not snoozed and not + * repeating + */ + private synchronized void cleanupAlarm() { + if (Log.LOGV) Log.v("cleanupAlarm " + mAlarmId); + if (!mCleanupCalled) { + mCleanupCalled = true; + + // Stop audio playing + mNotificationManager.cancel(AUDIO_ALERT_NOTIFICATION_ID); + + /* disable alarm only if it is not set to repeat */ + if (!mSnoozed && ((mDaysOfWeek == null || !mDaysOfWeek.isRepeatSet()))) { + Alarms.enableAlarm(AlarmAlert.this, mAlarmId, false); + } + } + } +} diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java new file mode 100644 index 000000000..bafc70807 --- /dev/null +++ b/src/com/android/alarmclock/AlarmAlertWakeLock.java @@ -0,0 +1,55 @@ +/* + * 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.alarmclock; + +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * Hold a wakelock that can be acquired in the AlarmReceiver and + * released in the AlarmAlert activity + */ +class AlarmAlertWakeLock { + + private static PowerManager.WakeLock sWakeLock; + + static void acquire(Context context) { + if (sWakeLock != null) { + sWakeLock.release(); + } + + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + sWakeLock = pm.newWakeLock( + PowerManager.FULL_WAKE_LOCK | + PowerManager.ACQUIRE_CAUSES_WAKEUP | + PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); + sWakeLock.acquire(); + } + + static void release() { + if (Log.LOGV) Log.v("AlarmAlertWakeLock release"); + if (sWakeLock != null) { + sWakeLock.release(); + sWakeLock = null; + } + } +} diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java new file mode 100644 index 000000000..8db366068 --- /dev/null +++ b/src/com/android/alarmclock/AlarmClock.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.CheckBox; + +import java.util.Calendar; + +/** + * AlarmClock application. + */ +public class AlarmClock extends Activity { + + final static String PREFERENCES = "AlarmClock"; + final static int SET_ALARM = 1; + final static String PREF_CLOCK_FACE = "face"; + final static String PREF_SHOW_CLOCK = "show_clock"; + + /** Cap alarm count at this number */ + final static int MAX_ALARM_COUNT = 12; + + private SharedPreferences mPrefs; + private LayoutInflater mFactory; + private ViewGroup mClockLayout; + private View mClock = null; + private MenuItem mAddAlarmItem; + private MenuItem mToggleClockItem; + private ListView mAlarmsList; + private Cursor mCursor; + + /** + * Which clock face to show + */ + private int mFace = -1; + + /* + * FIXME: it would be nice for this to live in an xml config file. + */ + final static int[] CLOCKS = { + R.layout.clock_basic_bw, + R.layout.clock_googly, + R.layout.clock_droid2, + R.layout.clock_droids, + R.layout.digital_clock + }; + + private class AlarmTimeAdapter extends CursorAdapter { + public AlarmTimeAdapter(Context context, Cursor cursor) { + super(context, cursor); + } + + public View newView(Context context, Cursor cursor, ViewGroup parent) { + /* FIXME: this is called 3+x times too many by the ListView */ + View ret = mFactory.inflate(R.layout.alarm_time, parent, false); + DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); + digitalClock.setLive(false); + if (Log.LOGV) Log.v("newView " + cursor.getPosition()); + return ret; + } + + public void bindView(View view, Context context, Cursor cursor) { + final int id = cursor.getInt(Alarms.AlarmColumns.ALARM_ID_INDEX); + final int hour = cursor.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX); + final int minutes = cursor.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX); + final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek( + cursor.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); + final boolean enabled = cursor.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1; + + CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton); + onButton.setChecked(enabled); + onButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + boolean isChecked = ((CheckBox) v).isChecked(); + Alarms.enableAlarm(AlarmClock.this, id, isChecked); + if (isChecked) { + SetAlarm.popAlarmSetToast( + AlarmClock.this, hour, minutes, daysOfWeek); + } + } + }); + + DigitalClock digitalClock = (DigitalClock)view.findViewById(R.id.digitalClock); + if (Log.LOGV) Log.v("bindView " + cursor.getPosition() + " " + id + " " + hour + + ":" + minutes + " " + daysOfWeek.toString(context, true) + " dc " + digitalClock); + + digitalClock.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (true) { + Intent intent = new Intent(AlarmClock.this, SetAlarm.class); + intent.putExtra(Alarms.ID, id); + startActivityForResult(intent, SET_ALARM); + } else { + // TESTING: immediately pop alarm + Intent fireAlarm = new Intent(AlarmClock.this, AlarmAlert.class); + fireAlarm.putExtra(Alarms.ID, id); + fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(fireAlarm); + } + } + }); + + // set the alarm text + Calendar c = Calendar.getInstance(); + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minutes); + digitalClock.updateTime(c); + TextView daysOfWeekView = (TextView) digitalClock.findViewById(R.id.daysOfWeek); + daysOfWeekView.setText(daysOfWeek.toString(AlarmClock.this, false)); + } + }; + + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + // sanity check -- no database, no clock + if (getContentResolver() == null) { + new AlertDialog.Builder(this) + .setTitle(getString(R.string.error)) + .setMessage(getString(R.string.dberror)) + .setPositiveButton( + android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + finish(); + }}) + .setIcon(android.R.drawable.ic_dialog_alert) + .create().show(); + return; + } + + setContentView(R.layout.alarm_clock); + mFactory = LayoutInflater.from(this); + mPrefs = getSharedPreferences(PREFERENCES, 0); + + mCursor = Alarms.getAlarmsCursor(getContentResolver()); + mAlarmsList = (ListView) findViewById(R.id.alarms_list); + mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); + mAlarmsList.setVerticalScrollBarEnabled(true); + mAlarmsList.setItemsCanFocus(true); + + mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); + mClockLayout.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + final Intent intent = new Intent(AlarmClock.this, ClockPicker.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + + setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); + } + + @Override + protected void onResume() { + super.onResume(); + + int face = mPrefs.getInt(PREF_CLOCK_FACE, 0); + if (mFace != face) { + if (face < 0 || face >= AlarmClock.CLOCKS.length) + mFace = 0; + else + mFace = face; + inflateClock(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + ToastMaster.cancelToast(); + mCursor.deactivate(); + } + + protected void inflateClock() { + if (mClock != null) { + mClockLayout.removeView(mClock); + } + mClock = mFactory.inflate(CLOCKS[mFace], null); + mClockLayout.addView(mClock, 0); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + mAddAlarmItem = menu.add(0, 0, 0, R.string.add_alarm); + mAddAlarmItem.setIcon(android.R.drawable.ic_menu_add); + + mToggleClockItem = menu.add(0, 0, 0, R.string.hide_clock); + mToggleClockItem.setIcon(R.drawable.ic_menu_clock_face); + + if (false) { + Intent intent = new Intent(); + intent.setClassName( + "com.android.settings", + "com.android.settings.DateTimeSettings"); + MenuItem settingsItem = menu.add(0, 0, 0, R.string.settings).setIntent(intent); + settingsItem.setIcon(android.R.drawable.ic_menu_preferences); + } + + return true; + } + + /** + * Only allow user to add a new alarm if there are fewer than + * MAX_ALARM_COUNT + */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + mAddAlarmItem.setVisible(mAlarmsList.getChildCount() < MAX_ALARM_COUNT); + mToggleClockItem.setTitle(getClockVisibility() ? R.string.hide_clock : + R.string.show_clock); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item == mAddAlarmItem) { + Uri uri = Alarms.addAlarm(getContentResolver()); + // FIXME: scroll to new item. mAlarmsList.requestChildRectangleOnScreen() ? + String segment = uri.getPathSegments().get(1); + int newId = Integer.parseInt(segment); + if (Log.LOGV) Log.v("In AlarmClock, new alarm id = " + newId); + Intent intent = new Intent(AlarmClock.this, SetAlarm.class); + intent.putExtra(Alarms.ID, newId); + startActivityForResult(intent, SET_ALARM); + return true; + } else if (item == mToggleClockItem) { + setClockVisibility(!getClockVisibility()); + saveClockVisibility(); + return true; + } + + return false; + } + + + private boolean getClockVisibility() { + return mClockLayout.getVisibility() == View.VISIBLE; + } + + private void setClockVisibility(boolean visible) { + mClockLayout.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + private void saveClockVisibility() { + mPrefs.edit().putBoolean(PREF_SHOW_CLOCK, getClockVisibility()).commit(); + } +} diff --git a/src/com/android/alarmclock/AlarmInitReceiver.java b/src/com/android/alarmclock/AlarmInitReceiver.java new file mode 100644 index 000000000..77549b07a --- /dev/null +++ b/src/com/android/alarmclock/AlarmInitReceiver.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; + +public class AlarmInitReceiver extends BroadcastReceiver { + + /** + * Sets alarm on ACTION_BOOT_COMPLETED. Resets alarm on + * TIME_SET, TIMEZONE_CHANGED + */ + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Log.LOGV) Log.v("AlarmInitReceiver" + action); + + if (context.getContentResolver() == null) { + Log.e("AlarmInitReceiver: FAILURE unable to get content resolver. Alarms inactive."); + return; + } + if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { + Alarms.disableSnoozeAlert(context); + Alarms.disableExpiredAlarms(context); + } + Alarms.setNextAlert(context); + } +} diff --git a/src/com/android/alarmclock/AlarmPreference.java b/src/com/android/alarmclock/AlarmPreference.java new file mode 100644 index 000000000..0fd4f89dd --- /dev/null +++ b/src/com/android/alarmclock/AlarmPreference.java @@ -0,0 +1,54 @@ +/* + * 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.alarmclock; + +import android.content.Context; +import android.net.Uri; +import android.preference.RingtonePreference; +import android.util.AttributeSet; + +public class AlarmPreference extends RingtonePreference { + public Uri mAlert; + private IRingtoneChangedListener mRingtoneChangedListener; + + public interface IRingtoneChangedListener { + public void onRingtoneChanged(Uri ringtoneUri); + }; + + public AlarmPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setRingtoneChangedListener(IRingtoneChangedListener listener) { + mRingtoneChangedListener = listener; + } + + @Override + protected void onSaveRingtone(Uri ringtoneUri) { + if (ringtoneUri != null) { + mAlert = ringtoneUri; + if (mRingtoneChangedListener != null) { + mRingtoneChangedListener.onRingtoneChanged(ringtoneUri); + } + } + } + + @Override + protected Uri onRestoreRingtone() { + return mAlert; + } +} diff --git a/src/com/android/alarmclock/AlarmProvider.java b/src/com/android/alarmclock/AlarmProvider.java new file mode 100644 index 000000000..407f577a7 --- /dev/null +++ b/src/com/android/alarmclock/AlarmProvider.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; + +public class AlarmProvider extends ContentProvider { + private SQLiteOpenHelper mOpenHelper; + + private static final int ALARMS = 1; + private static final int ALARMS_ID = 2; + private static final UriMatcher sURLMatcher = new UriMatcher( + UriMatcher.NO_MATCH); + + static { + sURLMatcher.addURI("com.android.alarmclock", "alarm", ALARMS); + sURLMatcher.addURI("com.android.alarmclock", "alarm/#", ALARMS_ID); + } + + private static class DatabaseHelper extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "alarms.db"; + private static final int DATABASE_VERSION = 5; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE alarms (" + + "_id INTEGER PRIMARY KEY," + + "hour INTEGER, " + + "minutes INTEGER, " + + "daysofweek INTEGER, " + + "alarmtime INTEGER, " + + "enabled INTEGER, " + + "vibrate INTEGER, " + + "message TEXT, " + + "alert TEXT);"); + + // insert default alarms + String insertMe = "INSERT INTO alarms " + + "(hour, minutes, daysofweek, alarmtime, enabled, vibrate, message, alert) " + + "VALUES "; + db.execSQL(insertMe + "(7, 0, 0, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(8, 30, 2, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(9, 00, 18, 0, 0, 1, '', '');"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { + if (Log.LOGV) Log.v( + "Upgrading alarms database from version " + + oldVersion + " to " + currentVersion + + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS alarms"); + onCreate(db); + } + } + + public AlarmProvider() { + } + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + return true; + } + + @Override + public Cursor query(Uri url, String[] projectionIn, String selection, + String[] selectionArgs, String sort) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + // Generate the body of the query + int match = sURLMatcher.match(url); + switch (match) { + case ALARMS: + qb.setTables("alarms"); + break; + case ALARMS_ID: + qb.setTables("alarms"); + qb.appendWhere("_id="); + qb.appendWhere(url.getPathSegments().get(1)); + break; + default: + throw new IllegalArgumentException("Unknown URL " + url); + } + + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, + null, null, sort); + + if (ret == null) { + if (Log.LOGV) Log.v("Alarms.query: failed"); + } else { + ret.setNotificationUri(getContext().getContentResolver(), url); + } + + return ret; + } + + @Override + public String getType(Uri url) { + int match = sURLMatcher.match(url); + switch (match) { + case ALARMS: + return "vnd.android.cursor.dir/alarms"; + case ALARMS_ID: + return "vnd.android.cursor.item/alarms"; + default: + throw new IllegalArgumentException("Unknown URL"); + } + } + + @Override + public int update(Uri url, ContentValues values, String where, String[] whereArgs) { + int count; + long rowId = 0; + int match = sURLMatcher.match(url); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + switch (match) { + case ALARMS_ID: { + String segment = url.getPathSegments().get(1); + rowId = Long.parseLong(segment); + count = db.update("alarms", values, "_id=" + rowId, null); + break; + } + default: { + throw new UnsupportedOperationException( + "Cannot update URL: " + url); + } + } + if (Log.LOGV) Log.v("*** notifyChange() rowId: " + rowId + " url " + url); + getContext().getContentResolver().notifyChange(url, null); + return count; + } + + @Override + public Uri insert(Uri url, ContentValues initialValues) { + if (sURLMatcher.match(url) != ALARMS) { + throw new IllegalArgumentException("Cannot insert into URL: " + url); + } + + ContentValues values; + if (initialValues != null) + values = new ContentValues(initialValues); + else + values = new ContentValues(); + + if (!values.containsKey(Alarms.AlarmColumns.HOUR)) + values.put(Alarms.AlarmColumns.HOUR, 0); + + if (!values.containsKey(Alarms.AlarmColumns.MINUTES)) + values.put(Alarms.AlarmColumns.MINUTES, 0); + + if (!values.containsKey(Alarms.AlarmColumns.DAYS_OF_WEEK)) + values.put(Alarms.AlarmColumns.DAYS_OF_WEEK, 0); + + if (!values.containsKey(Alarms.AlarmColumns.ALARM_TIME)) + values.put(Alarms.AlarmColumns.ALARM_TIME, 0); + + if (!values.containsKey(Alarms.AlarmColumns.ENABLED)) + values.put(Alarms.AlarmColumns.ENABLED, 0); + + if (!values.containsKey(Alarms.AlarmColumns.VIBRATE)) + values.put(Alarms.AlarmColumns.VIBRATE, 1); + + if (!values.containsKey(Alarms.AlarmColumns.MESSAGE)) + values.put(Alarms.AlarmColumns.MESSAGE, ""); + + if (!values.containsKey(Alarms.AlarmColumns.ALERT)) + values.put(Alarms.AlarmColumns.ALERT, ""); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert("alarms", Alarms.AlarmColumns.MESSAGE, values); + if (rowId < 0) { + throw new SQLException("Failed to insert row into " + url); + } + if (Log.LOGV) Log.v("Added alarm rowId = " + rowId); + + Uri newUrl = ContentUris.withAppendedId(Alarms.AlarmColumns.CONTENT_URI, rowId); + getContext().getContentResolver().notifyChange(newUrl, null); + return newUrl; + } + + public int delete(Uri url, String where, String[] whereArgs) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count; + long rowId = 0; + switch (sURLMatcher.match(url)) { + case ALARMS: + count = db.delete("alarms", where, whereArgs); + break; + case ALARMS_ID: + String segment = url.getPathSegments().get(1); + rowId = Long.parseLong(segment); + if (TextUtils.isEmpty(where)) { + where = "_id=" + segment; + } else { + where = "_id=" + segment + " AND (" + where + ")"; + } + count = db.delete("alarms", where, whereArgs); + break; + default: + throw new IllegalArgumentException("Cannot delete from URL: " + url); + } + + getContext().getContentResolver().notifyChange(url, null); + return count; + } +} diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java new file mode 100644 index 000000000..434e82342 --- /dev/null +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.os.Handler; +import android.os.PowerManager; +import android.os.SystemClock; + +/** + * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert + * activity. Passes through Alarm ID. + */ +public class AlarmReceiver extends BroadcastReceiver { + + /** If the alarm is older than STALE_WINDOW seconds, ignore. It + is probably the result of a time or timezone change */ + private final static int STALE_WINDOW = 60 * 30; + + @Override + public void onReceive(Context context, Intent intent) { + long now = System.currentTimeMillis(); + int id = intent.getIntExtra(Alarms.ID, 0); + long setFor = intent.getLongExtra(Alarms.TIME, 0); + + if (now > setFor + STALE_WINDOW * 1000) { + if (Log.LOGV) Log.v("AlarmReceiver ignoring stale alarm intent id" + + id + " setFor " + setFor + " now " + now); + return; + } + + if (Log.LOGV) Log.v("*********** Keep UI Alive *************"); + AlarmAlertWakeLock.acquire(context); + + Intent fireAlarm = new Intent(context, AlarmAlert.class); + + fireAlarm.putExtra(Alarms.ID, id); + fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(fireAlarm); + } +} diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java new file mode 100644 index 000000000..849f6b9ba --- /dev/null +++ b/src/com/android/alarmclock/Alarms.java @@ -0,0 +1,753 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.pim.DateFormat; +import android.provider.BaseColumns; +import android.provider.Settings; + +import java.util.Calendar; + +/** + * The Alarms provider supplies info about Alarm Clock settings + */ +public class Alarms { + + public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; + public final static String ID = "alarm_id"; + public final static String TIME = "alarm_time"; + private final static int STATUSBAR_NOTIFICATION_ID = 22; + + final static String PREF_SNOOZE_ID = "snooze_id"; + final static String PREF_SNOOZE_TIME = "snooze_time"; + + private final static String DM12 = "E h:mm aa"; + private final static String DM24 = "E k:mm"; + + private final static String M12 = "h:mm aa"; + private final static String M24 = "k:mm"; + + static class DaysOfWeek { + + int mDays; + + /** + * Days of week coded as single int, convenient for DB + * storage: + * + * 0x00: no day + * 0x01: Monday + * 0x02: Tuesday + * 0x04: Wednesday + * 0x08: Thursday + * 0x10: Friday + * 0x20: Saturday + * 0x40: Sunday + */ + DaysOfWeek() { + this(0); + } + + DaysOfWeek(int days) { + mDays = days; + } + + public String toString(Context context, boolean showNever) { + StringBuilder ret = new StringBuilder(); + + /* no days */ + if (mDays == 0) return showNever ? context.getText( + R.string.never).toString() : ""; + + /* every day */ + if (mDays == 0x7f) { + return context.getText(R.string.every_day).toString(); + } + + /* count selected days */ + int dayCount = 0, days = mDays; + while (days > 0) { + if ((days & 1) == 1) dayCount++; + days >>= 1; + } + + /* short or long form? */ + CharSequence[] strings = + context.getResources().getTextArray( + (dayCount > 1) ? R.array.days_of_week_short : + R.array.days_of_week); + + /* selected days */ + for (int i = 0; i < 7; i++) { + if ((mDays & (1 << i)) != 0) { + ret.append(strings[i]); + dayCount -= 1; + if (dayCount > 0) ret.append( + context.getText(R.string.day_concat)); + } + } + return ret.toString(); + } + + /** + * @param day Mon=0 ... Sun=6 + * @return true if given day is set + */ + public boolean isSet(int day) { + return ((mDays & (1 << day)) > 0); + } + + public void set(int day, boolean set) { + if (set) { + mDays |= (1 << day); + } else { + mDays &= ~(1 << day); + } + } + + public void set(DaysOfWeek dow) { + mDays = dow.mDays; + } + + public int getCoded() { + return mDays; + } + + public boolean equals(DaysOfWeek dow) { + return mDays == dow.mDays; + } + + // Returns days of week encoded in an array of booleans. + public boolean[] getBooleanArray() { + boolean[] ret = new boolean[7]; + for (int i = 0; i < 7; i++) { + ret[i] = isSet(i); + } + return ret; + } + + public void setCoded(int days) { + mDays = days; + } + + /** + * @return true if alarm is set to repeat + */ + public boolean isRepeatSet() { + return mDays != 0; + } + + /** + * @return true if alarm is set to repeat every day + */ + public boolean isEveryDaySet() { + return mDays == 0x7f; + } + + + /** + * returns number of days from today until next alarm + * @param c must be set to today + */ + public int getNextAlarm(Calendar c) { + if (mDays == 0) return -1; + int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7; + + int day, dayCount; + for (dayCount = 0; dayCount < 7; dayCount++) { + day = (today + dayCount) % 7; + if ((mDays & (1 << day)) > 0) { + break; + } + } + return dayCount; + } + } + + public static class AlarmColumns implements BaseColumns { + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://com.android.alarmclock/alarm"); + + public static final String _ID = "_id"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "_id ASC"; + + /** + * Hour in 24-hour localtime 0 - 23. + *

Type: INTEGER

+ */ + public static final String HOUR = "hour"; + + /** + * Minutes in localtime 0 - 59 + *

Type: INTEGER

+ */ + public static final String MINUTES = "minutes"; + + /** + * Days of week coded as integer + *

Type: INTEGER

+ */ + public static final String DAYS_OF_WEEK = "daysofweek"; + + /** + * Alarm time in UTC milliseconds from the epoch. + *

Type: INTEGER

+ */ + public static final String ALARM_TIME = "alarmtime"; + + /** + * True if alarm is active + *

Type: BOOLEAN

+ */ + public static final String ENABLED = "enabled"; + + /** + * True if alarm should vibrate + *

Type: BOOLEAN

+ */ + public static final String VIBRATE = "vibrate"; + + /** + * Message to show when alarm triggers + * Note: not currently used + *

Type: STRING

+ */ + public static final String MESSAGE = "message"; + + /** + * Audio alert to play when alarm triggers + *

Type: STRING

+ */ + public static final String ALERT = "alert"; + + static final String[] ALARM_QUERY_COLUMNS = { + _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME, + ENABLED, VIBRATE, MESSAGE, ALERT}; + + /** + * These save calls to cursor.getColumnIndexOrThrow() + * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS + */ + public static final int ALARM_ID_INDEX = 0; + public static final int ALARM_HOUR_INDEX = 1; + public static final int ALARM_MINUTES_INDEX = 2; + public static final int ALARM_DAYS_OF_WEEK_INDEX = 3; + public static final int ALARM_TIME_INDEX = 4; + public static final int ALARM_ENABLED_INDEX = 5; + public static final int ALARM_VIBRATE_INDEX = 6; + public static final int ALARM_MESSAGE_INDEX = 7; + public static final int ALARM_ALERT_INDEX = 8; + } + + /** + * getAlarm and getAlarms call this interface to report alarms in + * the database + */ + static interface AlarmSettings { + void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert); + } + + /** + * Creates a new Alarm. + */ + public synchronized static Uri addAlarm(ContentResolver contentResolver) { + ContentValues values = new ContentValues(); + values.put(Alarms.AlarmColumns.HOUR, 8); + return contentResolver.insert(AlarmColumns.CONTENT_URI, values); + } + + /** + * Removes an existing Alarm. + */ + public synchronized static void deleteAlarm( + ContentResolver contentResolver, int alarmId) { + Uri uri = ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId); + deleteAlarm(contentResolver, uri); + } + + public synchronized static void deleteAlarm( + ContentResolver contentResolver, Uri uri) { + contentResolver.delete(uri, "", null); + } + + /** + * Queries all alarms + * @return cursor over all alarms + */ + public synchronized static Cursor getAlarmsCursor( + ContentResolver contentResolver) { + return contentResolver.query( + AlarmColumns.CONTENT_URI, AlarmColumns.ALARM_QUERY_COLUMNS, + null, null, AlarmColumns.DEFAULT_SORT_ORDER); + } + + /** + * Calls the AlarmSettings.reportAlarm interface on all alarms found in db. + */ + public synchronized static void getAlarms( + ContentResolver contentResolver, AlarmSettings alarmSettings) { + Cursor cursor = getAlarmsCursor(contentResolver); + getAlarms(alarmSettings, cursor); + cursor.close(); + } + + private synchronized static void getAlarms( + AlarmSettings alarmSettings, Cursor cur) { + if (cur.moveToFirst()) { + do { + // Get the field values + int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX); + int hour = cur.getInt(AlarmColumns.ALARM_HOUR_INDEX); + int minutes = cur.getInt(AlarmColumns.ALARM_MINUTES_INDEX); + int daysOfWeek = cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX); + boolean enabled = cur.getInt(AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false; + boolean vibrate = cur.getInt(AlarmColumns.ALARM_VIBRATE_INDEX) == 1 ? true : false; + String message = cur.getString(AlarmColumns.ALARM_MESSAGE_INDEX); + String alert = cur.getString(AlarmColumns.ALARM_ALERT_INDEX); + alarmSettings.reportAlarm( + id, enabled, hour, minutes, new DaysOfWeek(daysOfWeek), + vibrate, message, alert); + } while (cur.moveToNext()); + } + } + + /** + * Calls the AlarmSettings.reportAlarm interface on alarm with given + * alarmId + */ + public synchronized static void getAlarm( + ContentResolver contentResolver, AlarmSettings alarmSetting, + int alarmId) { + Cursor cursor = contentResolver.query( + ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId), + AlarmColumns.ALARM_QUERY_COLUMNS, + null, null, AlarmColumns.DEFAULT_SORT_ORDER); + + getAlarms(alarmSetting, cursor); + cursor.close(); + } + + + /** + * A convenience method to set an alarm in the Alarms + * content provider. + * + * @param id corresponds to the _id column + * @param enabled corresponds to the ENABLED column + * @param hour corresponds to the HOUR column + * @param minutes corresponds to the MINUTES column + * @param daysOfWeek corresponds to the DAYS_OF_WEEK column + * @param time corresponds to the ALARM_TIME column + * @param vibrate corresponds to the VIBRATE column + * @param message corresponds to the MESSAGE column + * @param alert corresponds to the ALERT column + */ + public synchronized static void setAlarm( + Context context, int id, boolean enabled, int hour, int minutes, + DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + + ContentValues values = new ContentValues(8); + ContentResolver resolver = context.getContentResolver(); + long time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis(); + + if (Log.LOGV) Log.v( + "** setAlarm * idx " + id + " hour " + hour + " minutes " + + minutes + " enabled " + enabled + " time " + time); + + values.put(AlarmColumns.ENABLED, enabled ? 1 : 0); + values.put(AlarmColumns.HOUR, hour); + values.put(AlarmColumns.MINUTES, minutes); + values.put(AlarmColumns.ALARM_TIME, time); + values.put(AlarmColumns.DAYS_OF_WEEK, daysOfWeek.getCoded()); + values.put(AlarmColumns.VIBRATE, vibrate); + values.put(AlarmColumns.MESSAGE, message); + values.put(AlarmColumns.ALERT, alert); + resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id), + values, null, null); + + int aid = disableSnoozeAlert(context); + if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false); + setNextAlert(context); + } + + /** + * A convenience method to enable or disable an alarm. + * + * @param id corresponds to the _id column + * @param enabled corresponds to the ENABLED column + */ + + public synchronized static void enableAlarm( + final Context context, final int id, boolean enabled) { + int aid = disableSnoozeAlert(context); + if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false); + enableAlarmInternal(context, id, enabled); + setNextAlert(context); + } + + private synchronized static void enableAlarmInternal( + final Context context, final int id, boolean enabled) { + ContentResolver resolver = context.getContentResolver(); + + class EnableAlarm implements AlarmSettings { + public int mHour; + public int mMinutes; + public DaysOfWeek mDaysOfWeek; + + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + mHour = hour; + mMinutes = minutes; + mDaysOfWeek = daysOfWeek; + } + } + + ContentValues values = new ContentValues(2); + values.put(AlarmColumns.ENABLED, enabled ? 1 : 0); + + /* If we are enabling the alarm, load hour/minutes/daysOfWeek + from db, so we can calculate alarm time */ + if (enabled) { + EnableAlarm enableAlarm = new EnableAlarm(); + getAlarm(resolver, enableAlarm, id); + if (enableAlarm.mDaysOfWeek == null) { + /* Under monkey, sometimes reportAlarm is never + called */ + Log.e("** enableAlarmInternal failed " + id + " h " + + enableAlarm.mHour + " m " + enableAlarm.mMinutes); + return; + } + + long time = calculateAlarm(enableAlarm.mHour, enableAlarm.mMinutes, + enableAlarm.mDaysOfWeek).getTimeInMillis(); + values.put(AlarmColumns.ALARM_TIME, time); + } + + resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id), + values, null, null); + } + + + /** + * Calculates next scheduled alert + */ + static class AlarmCalculator implements AlarmSettings { + public long mMinAlert = Long.MAX_VALUE; + public int mMinIdx = -1; + + /** + * returns next scheduled alert, MAX_VALUE if none + */ + public long getAlert() { + return mMinAlert; + } + public int getIndex() { + return mMinIdx; + } + + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + if (enabled) { + long atTime = calculateAlarm(hour, minutes, + daysOfWeek).getTimeInMillis(); + /* Log.i("** SET ALERT* idx " + idx + " hour " + hour + " minutes " + + minutes + " enabled " + enabled + " calc " + atTime); */ + if (atTime < mMinAlert) { + mMinIdx = idx; + mMinAlert = atTime; + } + } + } + } + + static AlarmCalculator calculateNextAlert(final Context context) { + ContentResolver resolver = context.getContentResolver(); + AlarmCalculator alarmCalc = new AlarmCalculator(); + getAlarms(resolver, alarmCalc); + return alarmCalc; + } + + /** + * Disables non-repeating alarms that have passed. Called at + * boot. + */ + public static void disableExpiredAlarms(final Context context) { + Cursor cur = getAlarmsCursor(context.getContentResolver()); + long now = System.currentTimeMillis(); + + if (cur.moveToFirst()) { + do { + // Get the field values + int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX); + boolean enabled = cur.getInt( + AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false; + DaysOfWeek daysOfWeek = new DaysOfWeek( + cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); + long time = cur.getLong(AlarmColumns.ALARM_TIME_INDEX); + + if (enabled && !daysOfWeek.isRepeatSet() && time < now) { + if (Log.LOGV) Log.v( + "** DISABLE " + id + " now " + now +" set " + time); + enableAlarmInternal(context, id, false); + } + } while (cur.moveToNext()); + } + cur.close(); + } + + private static NotificationManager getNotificationManager( + final Context context) { + return (NotificationManager) context.getSystemService( + context.NOTIFICATION_SERVICE); + } + + /** + * Called at system startup, on time/timezone change, and whenever + * the user changes alarm settings. Activates snooze if set, + * otherwise loads all alarms, activates next alert. + */ + public static void setNextAlert(final Context context) { + int snoozeId = getSnoozeAlarmId(context); + if (snoozeId == -1) { + AlarmCalculator ac = calculateNextAlert(context); + int id = ac.getIndex(); + long atTime = ac.getAlert(); + + if (atTime < Long.MAX_VALUE) { + enableAlert(context, id, atTime); + } else { + disableAlert(context, id); + } + } else { + enableSnoozeAlert(context); + } + } + + /** + * Sets alert in AlarmManger and StatusBar. This is what will + * actually launch the alert when the alarm triggers. + * + * Note: In general, apps should call setNextAlert() instead of + * this method. setAlert() is only used outside this class when + * the alert is not to be driven by the state of the db. "Snooze" + * uses this API, as we do not want to alter the alarm in the db + * with each snooze. + * + * @param id Alarm ID. + * @param atTimeInMillis milliseconds since epoch + */ + static void enableAlert(Context context, int id, long atTimeInMillis) { + AlarmManager am = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + + Intent intent = new Intent(ALARM_ALERT_ACTION); + if (Log.LOGV) Log.v("** setAlert id " + id + " atTime " + atTimeInMillis); + intent.putExtra(ID, id); + intent.putExtra(TIME, atTimeInMillis); + PendingIntent sender = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + + if (true) { + am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); + } else { + // a five-second alarm, for testing + am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, + sender); + } + + setStatusBarIcon(context, true); + + Calendar c = Calendar.getInstance(); + c.setTime(new java.util.Date(atTimeInMillis)); + String timeString = formatDayAndTime(context, c); + saveNextAlarm(context, timeString); + } + + /** + * Disables alert in AlarmManger and StatusBar. + * + * @param id Alarm ID. + */ + static void disableAlert(Context context, int id) { + AlarmManager am = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(ALARM_ALERT_ACTION); + intent.putExtra(ID, id); + PendingIntent sender = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + am.cancel(sender); + setStatusBarIcon(context, false); + saveNextAlarm(context, ""); + } + + static void saveSnoozeAlert(final Context context, int id, + long atTimeInMillis) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + SharedPreferences.Editor ed = prefs.edit(); + ed.putInt(PREF_SNOOZE_ID, id); + ed.putLong(PREF_SNOOZE_TIME, atTimeInMillis); + ed.commit(); + } + + /** + * @return ID of alarm disabled, if disabled, -1 otherwise + */ + static int disableSnoozeAlert(final Context context) { + int id = getSnoozeAlarmId(context); + if (id == -1) return -1; + saveSnoozeAlert(context, -1, 0); + return id; + } + + /** + * @return alarm ID of snoozing alarm, -1 if snooze unset + */ + static int getSnoozeAlarmId(final Context context) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + return prefs.getInt(PREF_SNOOZE_ID, -1); + } + + /** + * If there is a snooze set, enable it in AlarmManager + * @return true if snooze is set + */ + private static boolean enableSnoozeAlert(final Context context) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + + int id = prefs.getInt(PREF_SNOOZE_ID, -1); + if (id == -1) return false; + long atTimeInMillis = prefs.getLong(PREF_SNOOZE_TIME, -1); + if (id == -1) return false; + enableAlert(context, id, atTimeInMillis); + return true; + } + + + + /** + * Tells the StatusBar whether the alarm is enabled or disabled + */ + private static void setStatusBarIcon(Context context, boolean enabled) { + Intent alarmChanged = new Intent(Intent.ACTION_ALARM_CHANGED); + alarmChanged.putExtra("alarmSet", enabled); + context.sendBroadcast(alarmChanged); + } + + /** + * Given an alarm in hours and minutes, return a time suitable for + * setting in AlarmManager. + * @param hour Always in 24 hour 0-23 + * @param minute 0-59 + * @param daysOfWeek 0-59 + */ + static Calendar calculateAlarm(int hour, int minute, DaysOfWeek daysOfWeek) { + + // start with now + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(System.currentTimeMillis()); + + int nowHour = c.get(Calendar.HOUR_OF_DAY); + int nowMinute = c.get(Calendar.MINUTE); + + // if alarm is behind current time, advance one day + if (hour < nowHour || + hour == nowHour && minute <= nowMinute) { + c.add(Calendar.DAY_OF_YEAR, 1); + } + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + int addDays = daysOfWeek.getNextAlarm(c); + /* Log.v("** TIMES * " + c.getTimeInMillis() + " hour " + hour + + " minute " + minute + " dow " + c.get(Calendar.DAY_OF_WEEK) + " from now " + + addDays); */ + if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays); + return c; + } + + static String formatTime(final Context context, int hour, int minute, + DaysOfWeek daysOfWeek) { + Calendar c = calculateAlarm(hour, minute, daysOfWeek); + return formatTime(context, c); + } + + /* used by AlarmAlert */ + static String formatTime(final Context context, Calendar c) { + String format = get24HourMode(context) ? M24 : M12; + return (c == null) ? "" : (String)DateFormat.format(format, c); + } + + /** + * Shows day and time -- used for lock screen + */ + private static String formatDayAndTime(final Context context, Calendar c) { + String format = get24HourMode(context) ? DM24 : DM12; + return (c == null) ? "" : (String)DateFormat.format(format, c); + } + + /** + * Save time of the next alarm, as a formatted string, into the system + * settings so those who care can make use of it. + */ + static void saveNextAlarm(final Context context, String timeString) { + Settings.System.putString(context.getContentResolver(), + Settings.System.NEXT_ALARM_FORMATTED, + timeString); + } + + /** + * @return true if clock is set to 24-hour mode + */ + static boolean get24HourMode(final Context context) { + String value = Settings.System.getString(context.getContentResolver(), + Settings.System.TIME_12_24); + return !(value == null || value.equals("12")); + } +} diff --git a/src/com/android/alarmclock/ClockPicker.java b/src/com/android/alarmclock/ClockPicker.java new file mode 100644 index 000000000..039f5b8a3 --- /dev/null +++ b/src/com/android/alarmclock/ClockPicker.java @@ -0,0 +1,119 @@ +/* + * 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.alarmclock; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Gallery; + +/** + * Clock face picker for the Alarm Clock application. + */ +public class ClockPicker extends Activity implements + AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener { + + private LayoutInflater mFactory; + private Gallery mGallery; + + private SharedPreferences mPrefs; + private View mClock; + private ViewGroup mClockLayout; + private int mPosition; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + mFactory = LayoutInflater.from(this); + setContentView(R.layout.clockpicker); + + mGallery = (Gallery) findViewById(R.id.gallery); + mGallery.setAdapter(new ClockAdapter()); + mGallery.setOnItemSelectedListener(this); + mGallery.setOnItemClickListener(this); + + mPrefs = getSharedPreferences(AlarmClock.PREFERENCES, 0); + int face = mPrefs.getInt(AlarmClock.PREF_CLOCK_FACE, 0); + if (face < 0 || face >= AlarmClock.CLOCKS.length) face = 0; + + mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); + mClockLayout.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + selectClock(mPosition); + } + }); + + mGallery.setSelection(face, false); + } + + public void onItemSelected(AdapterView parent, View v, int position, long id) { + if (mClock != null) { + mClockLayout.removeView(mClock); + } + mClock = mFactory.inflate(AlarmClock.CLOCKS[position], null); + mClockLayout.addView(mClock, 0); + mPosition = position; + } + + public void onItemClick(AdapterView parent, View v, int position, long id) { + selectClock(position); + } + + private synchronized void selectClock(int position) { + SharedPreferences.Editor ed = mPrefs.edit(); + ed.putInt("face", position); + ed.commit(); + + setResult(RESULT_OK); + finish(); + } + + public void onNothingSelected(AdapterView parent) { + } + + class ClockAdapter extends BaseAdapter { + + public ClockAdapter() { + } + + public int getCount() { + return AlarmClock.CLOCKS.length; + } + + public Object getItem(int position) { + return position; + } + + public long getItemId(int position) { + return position; + } + + public View getView(final int position, View convertView, ViewGroup parent) { + View clock = mFactory.inflate(AlarmClock.CLOCKS[position], null); + return clock; + } + + } +} diff --git a/src/com/android/alarmclock/DigitalClock.java b/src/com/android/alarmclock/DigitalClock.java new file mode 100644 index 000000000..940d3f029 --- /dev/null +++ b/src/com/android/alarmclock/DigitalClock.java @@ -0,0 +1,209 @@ +/* + * 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.alarmclock; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.pim.DateFormat; +import android.provider.Settings; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.Calendar; + +/** + * Displays the time + */ +public class DigitalClock extends LinearLayout { + + private final static String M12 = "h:mm"; + private final static String M24 = "k:mm"; + + private Calendar mCalendar; + private String mFormat; + private TextView mTimeDisplay; + private AmPm mAmPm; + private boolean mAnimate; + private ContentObserver mFormatChangeObserver; + private boolean mLive = true; + private boolean mAttached; + + /* called by system on minute ticks */ + private final Handler mHandler = new Handler(); + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mLive && intent.getAction().equals( + Intent.ACTION_TIMEZONE_CHANGED)) { + mCalendar = Calendar.getInstance(); + } + updateTime(); + } + }; + + static class AmPm { + private int mColorOn, mColorOff; + + private LinearLayout mAmPmLayout; + private TextView mAm, mPm; + + AmPm(View parent) { + mAmPmLayout = (LinearLayout) parent.findViewById(R.id.am_pm); + mAm = (TextView)mAmPmLayout.findViewById(R.id.am); + mPm = (TextView)mAmPmLayout.findViewById(R.id.pm); + + Resources r = parent.getResources(); + mColorOn = r.getColor(R.color.ampm_on); + mColorOff = r.getColor(R.color.ampm_off); + } + + void setShowAmPm(boolean show) { + mAmPmLayout.setVisibility(show ? View.VISIBLE : View.GONE); + } + + void setIsMorning(boolean isMorning) { + mAm.setTextColor(isMorning ? mColorOn : mColorOff); + mPm.setTextColor(isMorning ? mColorOff : mColorOn); + } + } + + private class FormatChangeObserver extends ContentObserver { + public FormatChangeObserver() { + super(new Handler()); + } + @Override + public void onChange(boolean selfChange) { + setDateFormat(); + updateTime(); + } + } + + public DigitalClock(Context context) { + this(context, null); + } + + public DigitalClock(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mTimeDisplay = (TextView) findViewById(R.id.timeDisplay); + mAmPm = new AmPm(this); + mCalendar = Calendar.getInstance(); + + setDateFormat(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (Log.LOGV) Log.v("onAttachedToWindow " + this); + + if (mAttached) return; + mAttached = true; + + if (mAnimate) { + AnimationDrawable frameAnimation = + (AnimationDrawable) mContext.getResources().getDrawable( + R.drawable.animate_circle); + View digitalClock = findViewById(R.id.digitalClock); + digitalClock.setBackgroundDrawable(frameAnimation); + /* Start the animation (looped playback by default). */ + ((AnimationDrawable) digitalClock.getBackground()).start(); + /* No luck wrapping animation or individual bitmaps in a + ScaleDrawable */ + digitalClock.requestLayout(); + } + + if (mLive) { + /* monitor time ticks, time changed, timezone */ + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + } + + /* monitor 12/24-hour display preference */ + mFormatChangeObserver = new FormatChangeObserver(); + mContext.getContentResolver().registerContentObserver( + Settings.System.CONTENT_URI, true, mFormatChangeObserver); + + updateTime(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (!mAttached) return; + mAttached = false; + + Drawable background = getBackground(); + if (background instanceof AnimationDrawable) { + ((AnimationDrawable) background).stop(); + } + + if (mLive) { + mContext.unregisterReceiver(mIntentReceiver); + } + mContext.getContentResolver().unregisterContentObserver( + mFormatChangeObserver); + } + + + void updateTime(Calendar c) { + mCalendar = c; + updateTime(); + } + + private void updateTime() { + if (mLive) { + mCalendar.setTimeInMillis(System.currentTimeMillis()); + } + + CharSequence newTime = DateFormat.format(mFormat, mCalendar); + mTimeDisplay.setText(newTime); + mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0); + } + + private void setDateFormat() { + mFormat = Alarms.get24HourMode(mContext) ? M24 : M12; + mAmPm.setShowAmPm(mFormat == M12); + } + + void setAnimate() { + mAnimate = true; + } + + void setLive(boolean live) { + mLive = live; + } +} diff --git a/src/com/android/alarmclock/Log.java b/src/com/android/alarmclock/Log.java new file mode 100644 index 000000000..732e3e777 --- /dev/null +++ b/src/com/android/alarmclock/Log.java @@ -0,0 +1,39 @@ +/* + * 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-level logging flag + */ + +package com.android.alarmclock; + +import android.os.SystemClock; +import android.util.Config; + +class Log { + public final static String LOGTAG = "AlarmClock"; + + private static final boolean DEBUG = false; + static final boolean LOGV = DEBUG ? Config.LOGD : Config.LOGV; + + static void v(String logMe) { + android.util.Log.v(LOGTAG, /* SystemClock.uptimeMillis() + " " + */ logMe); + } + + static void e(String logMe) { + android.util.Log.e(LOGTAG, logMe); + } +} diff --git a/src/com/android/alarmclock/RepeatPreference.java b/src/com/android/alarmclock/RepeatPreference.java new file mode 100644 index 000000000..a8c60accc --- /dev/null +++ b/src/com/android/alarmclock/RepeatPreference.java @@ -0,0 +1,72 @@ +/* + * 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.alarmclock; + +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.ListPreference; +import android.util.AttributeSet; + +public class RepeatPreference extends ListPreference { + private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); + private OnRepeatChangeListener mOnRepeatChangeListener; + + public interface OnRepeatChangeListener { + /** Called when this preference has changed */ + public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek); + } + + public RepeatPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + void setDaysOfWeek(Alarms.DaysOfWeek daysOfWeek) { + /* we keep a local copy, so the host can set itself on a + positive result and ignore on a negative */ + mDaysOfWeek.set(daysOfWeek); + } + + void setOnRepeatChangeListener(OnRepeatChangeListener listener) { + mOnRepeatChangeListener = listener; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + mOnRepeatChangeListener.onRepeatChanged(mDaysOfWeek); + } + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + CharSequence[] entries = getEntries(); + CharSequence[] entryValues = getEntryValues(); + + if (entries == null || entryValues == null) { + throw new IllegalStateException( + "RepeatPreference requires an entries array and an entryValues array."); + } + builder.setMultiChoiceItems( + entries, mDaysOfWeek.getBooleanArray(), + new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + mDaysOfWeek.set(which, isChecked); + } + }); + } +} diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java new file mode 100644 index 000000000..3f40fcf35 --- /dev/null +++ b/src/com/android/alarmclock/SetAlarm.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.app.Activity; +import android.app.Dialog; +import android.app.TimePickerDialog; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.pim.DateFormat; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.CheckBoxPreference; +import android.preference.PreferenceScreen; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TimePicker; +import android.widget.Toast; + +/** + * Manages each alarm + */ +public class SetAlarm extends PreferenceActivity + implements Alarms.AlarmSettings, TimePickerDialog.OnTimeSetListener { + + private CheckBoxPreference mAlarmOnPref; + private Preference mTimePref; + private AlarmPreference mAlarmPref; + private CheckBoxPreference mVibratePref; + private RepeatPreference mRepeatPref; + private ContentObserver mAlarmsChangeObserver; + private MenuItem mDeleteAlarmItem; + + private int mId; + private int mHour; + private int mMinutes; + private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); + + private boolean mReportAlarmCalled; + + private static final int DIALOG_TIMEPICKER = 0; + + private class RingtoneChangedListener implements AlarmPreference.IRingtoneChangedListener { + public void onRingtoneChanged(Uri ringtoneUri) { + saveAlarm(false); + } + } + + private class OnRepeatChangeListener implements RepeatPreference.OnRepeatChangeListener { + public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek) { + if (!mDaysOfWeek.equals(daysOfWeek)) { + mDaysOfWeek.set(daysOfWeek); + saveAlarm(true); + } + } + } + + private class AlarmsChangeObserver extends ContentObserver { + public AlarmsChangeObserver() { + super(new Handler()); + } + @Override + public void onChange(boolean selfChange) { + Alarms.getAlarm(getContentResolver(), SetAlarm.this, mId); + } + } + + /** + * Set an alarm. Requires an Alarms.ID to be passed in as an + * extra + */ + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.alarm_prefs); + mAlarmOnPref = (CheckBoxPreference)findPreference("on"); + mTimePref = findPreference("time"); + mAlarmPref = (AlarmPreference) findPreference("alarm"); + mVibratePref = (CheckBoxPreference) findPreference("vibrate"); + mRepeatPref = (RepeatPreference) findPreference("setRepeat"); + + Intent i = getIntent(); + mId = i.getIntExtra(Alarms.ID, -1); + if (Log.LOGV) Log.v("In SetAlarm, alarm id = " + mId); + + mReportAlarmCalled = false; + /* load alarm details from database */ + Alarms.getAlarm(getContentResolver(), this, mId); + /* This should never happen, but does occasionally with the monkey. + * I believe it's a race condition where a deleted alarm is opened + * before the alarm list is refreshed. */ + if (!mReportAlarmCalled) { + Log.e("reportAlarm never called!"); + finish(); + } + + mAlarmsChangeObserver = new AlarmsChangeObserver(); + getContentResolver().registerContentObserver( + Alarms.AlarmColumns.CONTENT_URI, true, mAlarmsChangeObserver); + + mAlarmPref.setRingtoneChangedListener(new RingtoneChangedListener()); + mRepeatPref.setOnRepeatChangeListener(new OnRepeatChangeListener()); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getContentResolver().unregisterContentObserver(mAlarmsChangeObserver); + } + + @Override + protected Dialog onCreateDialog(int id) { + Dialog d; + + switch (id) { + case DIALOG_TIMEPICKER: + d = new TimePickerDialog( + SetAlarm.this, + this, + 0, + 0, + DateFormat.is24HourFormat(SetAlarm.this)); + d.setTitle(getResources().getString(R.string.time)); + break; + default: + d = null; + } + + return d; + } + + @Override + protected void onPrepareDialog(int id, Dialog dialog) { + super.onPrepareDialog(id, dialog); + + switch (id) { + case DIALOG_TIMEPICKER: + TimePickerDialog timePicker = (TimePickerDialog)dialog; + timePicker.updateTime(mHour, mMinutes); + break; + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + + if (preference == mTimePref) { + showDialog(DIALOG_TIMEPICKER); + } else if (preference == mAlarmOnPref) { + saveAlarm(true); + } else if (preference == mVibratePref) { + saveAlarm(false); + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + mHour = hourOfDay; + mMinutes = minute; + mAlarmOnPref.setChecked(true); + saveAlarm(true); + } + + /** + * Alarms.AlarmSettings implementation. Database feeds current + * settings in through this call + */ + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + Alarms.DaysOfWeek daysOfWeek, boolean vibrate,String message, + String alert) { + + mHour = hour; + mMinutes = minutes; + mAlarmOnPref.setChecked(enabled); + mDaysOfWeek.set(daysOfWeek); + mRepeatPref.setDaysOfWeek(mDaysOfWeek); + mVibratePref.setChecked(vibrate); + + if (alert == null || alert.length() == 0) { + if (Log.LOGV) Log.v("****** reportAlarm null or 0-length alert"); + mAlarmPref.mAlert = getDefaultAlarm(); + if (mAlarmPref.mAlert == null) { + Log.e("****** Default Alarm null"); + } + } else { + mAlarmPref.mAlert = Uri.parse(alert); + if (mAlarmPref.mAlert == null) { + Log.e("****** Parsed null alarm. URI: " + alert); + } + } + if (Log.LOGV) Log.v("****** reportAlarm uri " + alert + " alert " + + mAlarmPref.mAlert); + updateTime(); + updateRepeat(); + updateAlarm(mAlarmPref.mAlert); + + mReportAlarmCalled = true; + } + + /** + * picks the first alarm available + */ + private Uri getDefaultAlarm() { + RingtoneManager ringtoneManager = new RingtoneManager(this); + ringtoneManager.setType(RingtoneManager.TYPE_ALARM); + return ringtoneManager.getRingtoneUri(0); + } + + private void updateTime() { + if (Log.LOGV) Log.v("updateTime " + mId); + mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, mDaysOfWeek)); + } + + private void updateAlarm(Uri ringtoneUri) { + if (Log.LOGV) Log.v("updateAlarm " + mId); + Ringtone ringtone = RingtoneManager.getRingtone(SetAlarm.this, ringtoneUri); + if (ringtone != null) { + mAlarmPref.setSummary(ringtone.getTitle(SetAlarm.this)); + } + } + + private void updateRepeat() { + if (Log.LOGV) Log.v("updateRepeat " + mId); + mRepeatPref.setSummary(mDaysOfWeek.toString(this, true)); + } + + private void saveAlarm(boolean popToast) { + if (mReportAlarmCalled && mAlarmPref.mAlert != null) { + String alertString = mAlarmPref.mAlert.toString(); + saveAlarm(this, mId, mAlarmOnPref.isChecked(), mHour, mMinutes, + mDaysOfWeek, mVibratePref.isChecked(), alertString, + popToast); + } + } + + /** + * Write alarm out to persistent store and pops toast if alarm + * enabled + */ + private static void saveAlarm( + Context context, int id, boolean enabled, int hour, int minute, + Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String alert, + boolean popToast) { + if (Log.LOGV) Log.v("** saveAlarm " + id + " " + enabled + " " + hour + + " " + minute + " vibe " + vibrate); + + // Fix alert string first + Alarms.setAlarm(context, id, enabled, hour, minute, daysOfWeek, vibrate, + "", alert); + + if (enabled && popToast) { + popAlarmSetToast(context, hour, minute, daysOfWeek); + } + } + + /** + * Display a toast that tells the user how long until the alarm + * goes off. This helps prevent "am/pm" mistakes. + */ + static void popAlarmSetToast(Context context, int hour, int minute, + Alarms.DaysOfWeek daysOfWeek) { + + String toastText = formatToast(context, hour, minute, daysOfWeek); + Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); + ToastMaster.setToast(toast); + toast.show(); + } + + /** + * format "Alarm set for 2 days 7 hours and 53 minutes from + * now" + */ + static String formatToast(Context context, int hour, int minute, + Alarms.DaysOfWeek daysOfWeek) { + long alarm = Alarms.calculateAlarm(hour, minute, + daysOfWeek).getTimeInMillis(); + long delta = alarm - System.currentTimeMillis();; + long hours = delta / (1000 * 60 * 60); + long minutes = delta / (1000 * 60) % 60; + long days = hours / 24; + hours = hours % 24; + + String daySeq = (days == 0) ? "" : + (days == 1) ? context.getString(R.string.day) : + context.getString(R.string.days, Long.toString(days)); + + String minSeq = (minutes == 0) ? "" : + (minutes == 1) ? context.getString(R.string.minute) : + context.getString(R.string.minutes, Long.toString(minutes)); + + String hourSeq = (hours == 0) ? "" : + (hours == 1) ? context.getString(R.string.hour) : + context.getString(R.string.hours, Long.toString(hours)); + + boolean dispDays = days > 0; + boolean dispHour = hours > 0; + boolean dispMinute = minutes > 0; + + String ret; + if (!(dispDays || dispHour || dispMinute)) { + ret = context.getString(R.string.subminute); + } else { + String parts[] = new String[5]; + parts[0] = daySeq; + parts[1] = !dispDays ? "" : + dispHour && dispMinute ? context.getString(R.string.space) : + !dispHour && !dispMinute ? "" : + context.getString(R.string.and); + parts[2] = dispHour ? hourSeq : ""; + parts[3] = dispHour && dispMinute ? context.getString(R.string.and) : ""; + parts[4] = dispMinute ? minSeq : ""; + ret = context.getString(R.string.combiner, (Object[])parts); + } + + ret = context.getString(R.string.alarm_set, ret); + /* if (Log.LOGV) Log.v("** TOAST daySeq " + daySeq + " hourSeq " + hourSeq + + " minSeq " + minSeq + " ret " + ret); */ + return ret; + } + + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + mDeleteAlarmItem = menu.add(0, 0, 0, R.string.delete_alarm); + mDeleteAlarmItem.setIcon(android.R.drawable.ic_menu_delete); + + return true; + } + + public boolean onOptionsItemSelected(MenuItem item) { + if (item == mDeleteAlarmItem) { + + /* If alarm is snoozing, lose it */ + int id = Alarms.getSnoozeAlarmId(this); + if (id == mId) Alarms.disableSnoozeAlert(this); + + Alarms.deleteAlarm(getContentResolver(), mId); + Alarms.setNextAlert(this); + finish(); + return true; + } + + return false; + } + +} diff --git a/src/com/android/alarmclock/ToastMaster.java b/src/com/android/alarmclock/ToastMaster.java new file mode 100644 index 000000000..c4c281521 --- /dev/null +++ b/src/com/android/alarmclock/ToastMaster.java @@ -0,0 +1,41 @@ +/* + * 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.alarmclock; + +import android.widget.Toast; + +public class ToastMaster { + + private static Toast sToast = null; + + private ToastMaster() { + + } + + public static void setToast(Toast toast) { + if (sToast != null) + sToast.cancel(); + sToast = toast; + } + + public static void cancelToast() { + if (sToast != null) + sToast.cancel(); + sToast = null; + } + +} -- cgit v1.2.3 From 10aba5b72f92f925bdb56cc717489e2825587cd5 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 17 Dec 2008 18:05:51 -0800 Subject: Code drop from //branches/cupcake/...@124589 --- src/com/android/alarmclock/AlarmAlert.java | 172 ++++++----------- src/com/android/alarmclock/AlarmAlertWakeLock.java | 4 - src/com/android/alarmclock/AlarmClock.java | 32 +-- src/com/android/alarmclock/AlarmKlaxon.java | 214 +++++++++++++++++++++ src/com/android/alarmclock/AlarmProvider.java | 6 +- src/com/android/alarmclock/AlarmReceiver.java | 16 +- src/com/android/alarmclock/Alarms.java | 25 ++- src/com/android/alarmclock/DigitalClock.java | 2 +- src/com/android/alarmclock/Log.java | 7 +- src/com/android/alarmclock/RepeatPreference.java | 26 +-- src/com/android/alarmclock/SetAlarm.java | 51 +++-- 11 files changed, 385 insertions(+), 170 deletions(-) create mode 100644 src/com/android/alarmclock/AlarmKlaxon.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 31da6762f..c006d4d70 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -18,21 +18,18 @@ package com.android.alarmclock; import android.app.Activity; import android.app.KeyguardManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.media.AudioManager; -import android.net.Uri; +import android.graphics.PixelFormat; import android.os.Bundle; -import android.os.Handler; import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater; +import android.view.WindowManager; import android.widget.Button; import android.widget.Toast; +import android.widget.TextView; import java.util.Calendar; @@ -40,47 +37,45 @@ import java.util.Calendar; * Alarm Clock alarm alert: pops visible indicator and plays alarm * tone */ -public class AlarmAlert extends Activity implements Alarms.AlarmSettings { +public class AlarmAlert extends Activity { - private static long[] sVibratePattern = new long[] { 500, 500 }; - - private NotificationManager mNotificationManager; - - private final static int AUDIO_ALERT_NOTIFICATION_ID = 0; - /** Play alarm up to 5 minutes before silencing */ - private final static int ALARM_TIMEOUT_SECONDS = 300; private final static int SNOOZE_MINUTES = 10; private KeyguardManager mKeyguardManager; private KeyguardManager.KeyguardLock mKeyguardLock = null; - private Handler mTimeout; private Button mSnoozeButton; + private boolean mSnoozed; + private AlarmKlaxon mKlaxon; private int mAlarmId; - private Alarms.DaysOfWeek mDaysOfWeek; - private String mAlert; - private boolean mVibrate; - private boolean mSnoozed; - private boolean mCleanupCalled = false; - - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - if (Log.LOGV) Log.v("AlarmAlert.reportAlarm: " + idx + " " + hour + - " " + minutes + " dow " + daysOfWeek); - mAlert = alert; - mDaysOfWeek = daysOfWeek; - mVibrate = vibrate; - } @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); + + /* FIXME Intentionally verbose: always log this until we've + fully debugged the app failing to start up */ + Log.v("AlarmAlert.onCreate()"); + setContentView(R.layout.alarm_alert); + mKlaxon = AlarmKlaxon.getInstance(); + + // Popup alert over black screen + WindowManager.LayoutParams lp = getWindow().getAttributes(); + lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; + lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + // XXX DO NOT COPY THIS!!! THIS IS BOGUS! Making an activity have + // a system alert type is completely broken, because the activity + // manager will still hide/show it as if it is part of the normal + // activity stack. If this is really what you want and you want it + // to work correctly, you should create and show your own custom window. + lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; + lp.token = null; + getWindow().setAttributes(lp); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); - mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); /* set clock face */ LayoutInflater mFactory = LayoutInflater.from(this); @@ -94,7 +89,7 @@ public class AlarmAlert extends Activity implements Alarms.AlarmSettings { ((DigitalClock)clockLayout).setAnimate(); } - playAlert(getIntent()); + mAlarmId = getIntent().getIntExtra(Alarms.ID, -1); /* allow next alarm to trigger while this activity is active */ @@ -129,8 +124,7 @@ public class AlarmAlert extends Activity implements Alarms.AlarmSettings { Alarms.setNextAlert(AlarmAlert.this); mSnoozed = true; } - disableKiller(); - cleanupAlarm(); + mKlaxon.stop(AlarmAlert.this, mSnoozed); releaseLocks(); finish(); } @@ -139,12 +133,30 @@ public class AlarmAlert extends Activity implements Alarms.AlarmSettings { /* dismiss button: close notification */ findViewById(R.id.dismiss).setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { - disableKiller(); - cleanupAlarm(); + mKlaxon.stop(AlarmAlert.this, mSnoozed); releaseLocks(); finish(); } }); + + mKlaxon.setKillerCallback(new AlarmKlaxon.KillerCallback() { + public void onKilled() { + if (Log.LOGV) Log.v("onKilled()"); + TextView silenced = (TextView)findViewById(R.id.silencedText); + silenced.setText( + getString(R.string.alarm_alert_alert_silenced, + AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60)); + silenced.setVisibility(View.VISIBLE); + + /* don't allow snooze */ + mSnoozeButton.setEnabled(false); + + mKlaxon.stop(AlarmAlert.this, mSnoozed); + releaseLocks(); + } + }); + + mKlaxon.restoreInstanceState(this, icicle); } /** @@ -157,10 +169,13 @@ public class AlarmAlert extends Activity implements Alarms.AlarmSettings { if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); mSnoozeButton.setEnabled(true); disableKeyguard(); - cleanupAlarm(); - mCleanupCalled = false; - disableKiller(); - playAlert(intent); + + mAlarmId = intent.getIntExtra(Alarms.ID, -1); + + /* unset silenced message */ + TextView silenced = (TextView)findViewById(R.id.silencedText); + silenced.setVisibility(View.GONE); + Alarms.setNextAlert(this); setIntent(intent); } @@ -176,61 +191,13 @@ public class AlarmAlert extends Activity implements Alarms.AlarmSettings { protected void onStop() { super.onStop(); if (Log.LOGV) Log.v("AlarmAlert.onStop()"); - disableKiller(); - cleanupAlarm(); + mKlaxon.stop(this, mSnoozed); releaseLocks(); } - /** - * kicks off audio/vibe alert - */ - private void playAlert(Intent intent) { - mAlarmId = intent.getIntExtra(Alarms.ID, 0); - if (Log.LOGV) Log.v("playAlert() " + mAlarmId); - - /* load audio alert */ - ContentResolver cr = getContentResolver(); - /* this will call reportAlarm() callback */ - Alarms.getAlarm(cr, this, mAlarmId); - - /* play audio alert */ - if (mAlert == null) { - Log.e("Unable to play alarm: no audio file available"); - } else { - Notification audio = new Notification(); - audio.sound = Uri.parse(mAlert); - audio.audioStreamType = AudioManager.STREAM_ALARM; - audio.flags |= Notification.FLAG_INSISTENT; - if (mVibrate) audio.vibrate = sVibratePattern; - mNotificationManager.notify(AUDIO_ALERT_NOTIFICATION_ID, audio); - } - enableKiller(); - } - - /** - * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm - * won't run all day. - * - * This just cancels the audio, but leaves the notification - * popped, so the user will know that the alarm tripped. - */ - private void enableKiller() { - mTimeout = new Handler(); - mTimeout.postDelayed(new Runnable() { - public void run() { - if (Log.LOGV) Log.v("*********** Alarm killer triggered *************"); - /* don't allow snooze */ - mSnoozeButton.setEnabled(false); - cleanupAlarm(); - releaseLocks(); - }}, 1000 * ALARM_TIMEOUT_SECONDS); - } - - private void disableKiller() { - if (mTimeout != null) { - mTimeout.removeCallbacksAndMessages(null); - mTimeout = null; - } + @Override + protected void onSaveInstanceState(Bundle icicle) { + mKlaxon.onSaveInstanceState(icicle); } private synchronized void enableKeyguard() { @@ -254,23 +221,4 @@ public class AlarmAlert extends Activity implements Alarms.AlarmSettings { AlarmAlertWakeLock.release(); enableKeyguard(); } - - /** - * Stops alarm audio and disables alarm if it not snoozed and not - * repeating - */ - private synchronized void cleanupAlarm() { - if (Log.LOGV) Log.v("cleanupAlarm " + mAlarmId); - if (!mCleanupCalled) { - mCleanupCalled = true; - - // Stop audio playing - mNotificationManager.cancel(AUDIO_ALERT_NOTIFICATION_ID); - - /* disable alarm only if it is not set to repeat */ - if (!mSnoozed && ((mDaysOfWeek == null || !mDaysOfWeek.isRepeatSet()))) { - Alarms.enableAlarm(AlarmAlert.this, mAlarmId, false); - } - } - } } diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java index bafc70807..083996e58 100644 --- a/src/com/android/alarmclock/AlarmAlertWakeLock.java +++ b/src/com/android/alarmclock/AlarmAlertWakeLock.java @@ -17,11 +17,7 @@ package com.android.alarmclock; import android.content.Context; -import android.os.Binder; -import android.os.IBinder; import android.os.PowerManager; -import android.os.RemoteException; -import android.os.ServiceManager; /** * Hold a wakelock that can be acquired in the AlarmReceiver and diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index 8db366068..b068c4591 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -27,6 +27,8 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -53,6 +55,10 @@ public class AlarmClock extends Activity { /** Cap alarm count at this number */ final static int MAX_ALARM_COUNT = 12; + /** This must be false for production. If true, turns on logging, + test code, etc. */ + final static boolean DEBUG = false; + private SharedPreferences mPrefs; private LayoutInflater mFactory; private ViewGroup mClockLayout; @@ -84,7 +90,6 @@ public class AlarmClock extends Activity { } public View newView(Context context, Cursor cursor, ViewGroup parent) { - /* FIXME: this is called 3+x times too many by the ListView */ View ret = mFactory.inflate(R.layout.alarm_time, parent, false); DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); digitalClock.setLive(false); @@ -134,15 +139,29 @@ public class AlarmClock extends Activity { }); // set the alarm text - Calendar c = Calendar.getInstance(); + final Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR_OF_DAY, hour); c.set(Calendar.MINUTE, minutes); digitalClock.updateTime(c); TextView daysOfWeekView = (TextView) digitalClock.findViewById(R.id.daysOfWeek); daysOfWeekView.setText(daysOfWeek.toString(AlarmClock.this, false)); + + // Build context menu + digitalClock.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + public void onCreateContextMenu(ContextMenu menu, View view, + ContextMenuInfo menuInfo) { + menu.setHeaderTitle(Alarms.formatTime(AlarmClock.this, c)); + MenuItem deleteAlarmItem = menu.add(0, id, 0, R.string.delete_alarm); + } + }); } }; + @Override + public boolean onContextItemSelected(MenuItem item) { + Alarms.deleteAlarm(this, item.getItemId()); + return true; + } @Override protected void onCreate(Bundle icicle) { @@ -231,15 +250,6 @@ public class AlarmClock extends Activity { mToggleClockItem = menu.add(0, 0, 0, R.string.hide_clock); mToggleClockItem.setIcon(R.drawable.ic_menu_clock_face); - if (false) { - Intent intent = new Intent(); - intent.setClassName( - "com.android.settings", - "com.android.settings.DateTimeSettings"); - MenuItem settingsItem = menu.add(0, 0, 0, R.string.settings).setIntent(intent); - settingsItem.setIcon(android.R.drawable.ic_menu_preferences); - } - return true; } diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java new file mode 100644 index 000000000..6ea7f9a12 --- /dev/null +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -0,0 +1,214 @@ +/* + * 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.alarmclock; + +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnErrorListener; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Vibrator; + +/** + * Manages alarms and vibe. Singleton, so it can be initiated in + * AlarmReceiver and shut down in the AlarmAlert activity + */ +class AlarmKlaxon implements Alarms.AlarmSettings { + + interface KillerCallback { + public void onKilled(); + } + + /** Play alarm up to 10 minutes before silencing */ + final static int ALARM_TIMEOUT_SECONDS = 10 * 60; + final static String ICICLE_PLAYING = "IciclePlaying"; + final static String ICICLE_ALARMID = "IcicleAlarmId"; + + private static long[] sVibratePattern = new long[] { 500, 500 }; + + private static AlarmKlaxon sInstance; + + private int mAlarmId; + private String mAlert; + private Alarms.DaysOfWeek mDaysOfWeek; + private boolean mVibrate; + + private boolean mPlaying = false; + + private Vibrator mVibrator; + private MediaPlayer mMediaPlayer; + + private Handler mTimeout; + private KillerCallback mKillerCallback; + + + static synchronized AlarmKlaxon getInstance() { + if (sInstance == null) sInstance = new AlarmKlaxon(); + return sInstance; + } + + private AlarmKlaxon() { + mVibrator = new Vibrator(); + } + + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + if (Log.LOGV) Log.v("AlarmKlaxon.reportAlarm: " + idx + " " + hour + + " " + minutes + " dow " + daysOfWeek); + mAlert = alert; + mDaysOfWeek = daysOfWeek; + mVibrate = vibrate; + } + + synchronized void play(Context context, int alarmId) { + ContentResolver contentResolver = context.getContentResolver(); + + if (mPlaying) stop(context, false); + + mAlarmId = alarmId; + + /* this will call reportAlarm() callback */ + Alarms.getAlarm(contentResolver, this, mAlarmId); + + if (Log.LOGV) Log.v("AlarmKlaxon.play() " + mAlarmId + " alert " + mAlert); + + if (mVibrate) { + mVibrator.vibrate(sVibratePattern, 0); + } else { + mVibrator.cancel(); + } + + /* play audio alert */ + if (mAlert == null) { + Log.e("Unable to play alarm: no audio file available"); + } else { + + /* we need a new MediaPlayer when we change media URLs */ + mMediaPlayer = new MediaPlayer(); + if (mMediaPlayer == null) { + Log.e("Unable to instantiate MediaPlayer"); + } else { + mMediaPlayer.setOnErrorListener(new OnErrorListener() { + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e("Error occurred while playing audio."); + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + return true; + } + }); + + try { + mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + mMediaPlayer.setLooping(true); + mMediaPlayer.prepare(); + } catch (Exception ex) { + Log.e("Error playing alarm: " + mAlert, ex); + return; + } + mMediaPlayer.start(); + } + } + enableKiller(); + mPlaying = true; + } + + + /** + * Stops alarm audio and disables alarm if it not snoozed and not + * repeating + */ + synchronized void stop(Context context, boolean snoozed) { + if (Log.LOGV) Log.v("AlarmKlaxon.stop() " + mAlarmId); + if (mPlaying) { + mPlaying = false; + + // Stop audio playing + if (mMediaPlayer != null) mMediaPlayer.stop(); + + // Stop vibrator + mVibrator.cancel(); + + /* disable alarm only if it is not set to repeat */ + if (!snoozed && ((mDaysOfWeek == null || !mDaysOfWeek.isRepeatSet()))) { + Alarms.enableAlarm(context, mAlarmId, false); + } + } + disableKiller(); + } + + /** + * This callback called when alarm killer times out unattended + * alarm + */ + void setKillerCallback(KillerCallback killerCallback) { + mKillerCallback = killerCallback; + } + + + /** + * Called by the AlarmAlert activity on configuration change + */ + protected void onSaveInstanceState(Bundle icicle) { + icicle.putBoolean(ICICLE_PLAYING, mPlaying); + icicle.putInt(ICICLE_ALARMID, mAlarmId); + } + + /** + * Restores alarm playback state on configuration change + */ + void restoreInstanceState(Context context, Bundle icicle) { + if (!mPlaying && + icicle != null && + icicle.containsKey(ICICLE_PLAYING) && + icicle.getBoolean(ICICLE_PLAYING)) { + play(context, icicle.getInt(ICICLE_ALARMID)); + } + } + + /** + * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm + * won't run all day. + * + * This just cancels the audio, but leaves the notification + * popped, so the user will know that the alarm tripped. + */ + private void enableKiller() { + mTimeout = new Handler(); + mTimeout.postDelayed(new Runnable() { + public void run() { + if (Log.LOGV) Log.v("*********** Alarm killer triggered *************"); + if (mKillerCallback != null) mKillerCallback.onKilled(); + } + }, 1000 * ALARM_TIMEOUT_SECONDS); + } + + private void disableKiller() { + if (mTimeout != null) { + mTimeout.removeCallbacksAndMessages(null); + mTimeout = null; + } + } + + +} diff --git a/src/com/android/alarmclock/AlarmProvider.java b/src/com/android/alarmclock/AlarmProvider.java index 407f577a7..74fdd2e89 100644 --- a/src/com/android/alarmclock/AlarmProvider.java +++ b/src/com/android/alarmclock/AlarmProvider.java @@ -67,9 +67,9 @@ public class AlarmProvider extends ContentProvider { String insertMe = "INSERT INTO alarms " + "(hour, minutes, daysofweek, alarmtime, enabled, vibrate, message, alert) " + "VALUES "; - db.execSQL(insertMe + "(7, 0, 0, 0, 0, 1, '', '');"); - db.execSQL(insertMe + "(8, 30, 2, 0, 0, 1, '', '');"); - db.execSQL(insertMe + "(9, 00, 18, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(7, 0, 127, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(8, 30, 31, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(9, 00, 0, 0, 0, 1, '', '');"); } @Override diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 434e82342..70c24a2b5 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -19,9 +19,6 @@ package com.android.alarmclock; import android.content.Context; import android.content.Intent; import android.content.BroadcastReceiver; -import android.os.Handler; -import android.os.PowerManager; -import android.os.SystemClock; /** * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert @@ -39,17 +36,26 @@ public class AlarmReceiver extends BroadcastReceiver { int id = intent.getIntExtra(Alarms.ID, 0); long setFor = intent.getLongExtra(Alarms.TIME, 0); + /* FIXME Intentionally verbose: always log this until we've + fully debugged the app failing to start up */ + Log.v("AlarmReceiver.onReceive() id " + id + " setFor " + setFor + + " now " + now); + if (now > setFor + STALE_WINDOW * 1000) { if (Log.LOGV) Log.v("AlarmReceiver ignoring stale alarm intent id" + id + " setFor " + setFor + " now " + now); return; } - if (Log.LOGV) Log.v("*********** Keep UI Alive *************"); + /* wake device */ AlarmAlertWakeLock.acquire(context); - Intent fireAlarm = new Intent(context, AlarmAlert.class); + /* start audio/vibe */ + AlarmKlaxon klaxon = AlarmKlaxon.getInstance(); + klaxon.play(context, id); + /* launch UI */ + Intent fireAlarm = new Intent(context, AlarmAlert.class); fireAlarm.putExtra(Alarms.ID, id); fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(fireAlarm); diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index 849f6b9ba..56af9b310 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -28,9 +28,9 @@ import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; -import android.pim.DateFormat; import android.provider.BaseColumns; import android.provider.Settings; +import android.text.format.DateFormat; import java.util.Calendar; @@ -42,7 +42,6 @@ public class Alarms { public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; public final static String ID = "alarm_id"; public final static String TIME = "alarm_time"; - private final static int STATUSBAR_NOTIFICATION_ID = 22; final static String PREF_SNOOZE_ID = "snooze_id"; final static String PREF_SNOOZE_TIME = "snooze_time"; @@ -294,15 +293,24 @@ public class Alarms { } /** - * Removes an existing Alarm. + * Removes an existing Alarm. If this alarm is snoozing, disables + * snooze. Sets next alert. */ public synchronized static void deleteAlarm( - ContentResolver contentResolver, int alarmId) { + Context context, int alarmId) { + + ContentResolver contentResolver = context.getContentResolver(); + /* If alarm is snoozing, lose it */ + int snoozeId = getSnoozeAlarmId(context); + if (snoozeId == alarmId) disableSnoozeAlert(context); + Uri uri = ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId); deleteAlarm(contentResolver, uri); + + setNextAlert(context); } - public synchronized static void deleteAlarm( + private synchronized static void deleteAlarm( ContentResolver contentResolver, Uri uri) { contentResolver.delete(uri, "", null); } @@ -645,7 +653,7 @@ public class Alarms { /** * @return alarm ID of snoozing alarm, -1 if snooze unset */ - static int getSnoozeAlarmId(final Context context) { + private static int getSnoozeAlarmId(final Context context) { SharedPreferences prefs = context.getSharedPreferences( AlarmClock.PREFERENCES, 0); return prefs.getInt(PREF_SNOOZE_ID, -1); @@ -668,7 +676,6 @@ public class Alarms { } - /** * Tells the StatusBar whether the alarm is enabled or disabled */ @@ -746,8 +753,6 @@ public class Alarms { * @return true if clock is set to 24-hour mode */ static boolean get24HourMode(final Context context) { - String value = Settings.System.getString(context.getContentResolver(), - Settings.System.TIME_12_24); - return !(value == null || value.equals("12")); + return android.text.format.DateFormat.is24HourFormat(context); } } diff --git a/src/com/android/alarmclock/DigitalClock.java b/src/com/android/alarmclock/DigitalClock.java index 940d3f029..65dcc2c9c 100644 --- a/src/com/android/alarmclock/DigitalClock.java +++ b/src/com/android/alarmclock/DigitalClock.java @@ -25,8 +25,8 @@ import android.database.ContentObserver; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.pim.DateFormat; import android.provider.Settings; +import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; diff --git a/src/com/android/alarmclock/Log.java b/src/com/android/alarmclock/Log.java index 732e3e777..18cc391d0 100644 --- a/src/com/android/alarmclock/Log.java +++ b/src/com/android/alarmclock/Log.java @@ -26,8 +26,7 @@ import android.util.Config; class Log { public final static String LOGTAG = "AlarmClock"; - private static final boolean DEBUG = false; - static final boolean LOGV = DEBUG ? Config.LOGD : Config.LOGV; + static final boolean LOGV = AlarmClock.DEBUG ? Config.LOGD : Config.LOGV; static void v(String logMe) { android.util.Log.v(LOGTAG, /* SystemClock.uptimeMillis() + " " + */ logMe); @@ -36,4 +35,8 @@ class Log { static void e(String logMe) { android.util.Log.e(LOGTAG, logMe); } + + static void e(String logMe, Exception ex) { + android.util.Log.e(LOGTAG, logMe, ex); + } } diff --git a/src/com/android/alarmclock/RepeatPreference.java b/src/com/android/alarmclock/RepeatPreference.java index a8c60accc..8313858a4 100644 --- a/src/com/android/alarmclock/RepeatPreference.java +++ b/src/com/android/alarmclock/RepeatPreference.java @@ -23,10 +23,14 @@ import android.preference.ListPreference; import android.util.AttributeSet; public class RepeatPreference extends ListPreference { + private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); - private OnRepeatChangeListener mOnRepeatChangeListener; + private OnRepeatChangedObserver mOnRepeatChangedObserver; + + public interface OnRepeatChangedObserver { + /** RepeatPrefrence calls this to get initial state */ + public Alarms.DaysOfWeek getDaysOfWeek(); - public interface OnRepeatChangeListener { /** Called when this preference has changed */ public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek); } @@ -35,20 +39,17 @@ public class RepeatPreference extends ListPreference { super(context, attrs); } - void setDaysOfWeek(Alarms.DaysOfWeek daysOfWeek) { - /* we keep a local copy, so the host can set itself on a - positive result and ignore on a negative */ - mDaysOfWeek.set(daysOfWeek); - } - - void setOnRepeatChangeListener(OnRepeatChangeListener listener) { - mOnRepeatChangeListener = listener; + void setOnRepeatChangedObserver(OnRepeatChangedObserver onRepeatChangedObserver) { + mOnRepeatChangedObserver = onRepeatChangedObserver; } @Override protected void onDialogClosed(boolean positiveResult) { if (positiveResult) { - mOnRepeatChangeListener.onRepeatChanged(mDaysOfWeek); + mOnRepeatChangedObserver.onRepeatChanged(mDaysOfWeek); + } else { + /* no change -- reset to initial state */ + mDaysOfWeek.set(mOnRepeatChangedObserver.getDaysOfWeek()); } } @@ -61,6 +62,9 @@ public class RepeatPreference extends ListPreference { throw new IllegalStateException( "RepeatPreference requires an entries array and an entryValues array."); } + + mDaysOfWeek.set(mOnRepeatChangedObserver.getDaysOfWeek()); + builder.setMultiChoiceItems( entries, mDaysOfWeek.getBooleanArray(), new DialogInterface.OnMultiChoiceClickListener() { diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index 3f40fcf35..d8c9b46db 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -27,11 +27,11 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.pim.DateFormat; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.CheckBoxPreference; import android.preference.PreferenceScreen; +import android.text.format.DateFormat; import android.view.Menu; import android.view.MenuItem; import android.widget.TimePicker; @@ -50,6 +50,7 @@ public class SetAlarm extends PreferenceActivity private RepeatPreference mRepeatPref; private ContentObserver mAlarmsChangeObserver; private MenuItem mDeleteAlarmItem; + private MenuItem mTestAlarmItem; private int mId; private int mHour; @@ -66,13 +67,16 @@ public class SetAlarm extends PreferenceActivity } } - private class OnRepeatChangeListener implements RepeatPreference.OnRepeatChangeListener { + private class OnRepeatChangedObserver implements RepeatPreference.OnRepeatChangedObserver { public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek) { if (!mDaysOfWeek.equals(daysOfWeek)) { mDaysOfWeek.set(daysOfWeek); saveAlarm(true); } } + public Alarms.DaysOfWeek getDaysOfWeek() { + return mDaysOfWeek; + } } private class AlarmsChangeObserver extends ContentObserver { @@ -120,7 +124,7 @@ public class SetAlarm extends PreferenceActivity Alarms.AlarmColumns.CONTENT_URI, true, mAlarmsChangeObserver); mAlarmPref.setRingtoneChangedListener(new RingtoneChangedListener()); - mRepeatPref.setOnRepeatChangeListener(new OnRepeatChangeListener()); + mRepeatPref.setOnRepeatChangedObserver(new OnRepeatChangedObserver()); } @Override @@ -196,7 +200,6 @@ public class SetAlarm extends PreferenceActivity mMinutes = minutes; mAlarmOnPref.setChecked(enabled); mDaysOfWeek.set(daysOfWeek); - mRepeatPref.setDaysOfWeek(mDaysOfWeek); mVibratePref.setChecked(vibrate); if (alert == null || alert.length() == 0) { @@ -347,23 +350,49 @@ public class SetAlarm extends PreferenceActivity mDeleteAlarmItem = menu.add(0, 0, 0, R.string.delete_alarm); mDeleteAlarmItem.setIcon(android.R.drawable.ic_menu_delete); + if (AlarmClock.DEBUG) { + mTestAlarmItem = menu.add(0, 0, 0, "test alarm"); + } + + return true; } public boolean onOptionsItemSelected(MenuItem item) { if (item == mDeleteAlarmItem) { - - /* If alarm is snoozing, lose it */ - int id = Alarms.getSnoozeAlarmId(this); - if (id == mId) Alarms.disableSnoozeAlert(this); - - Alarms.deleteAlarm(getContentResolver(), mId); - Alarms.setNextAlert(this); + Alarms.deleteAlarm(this, mId); finish(); return true; } + if (AlarmClock.DEBUG) { + if (item == mTestAlarmItem) { + setTestAlarm(); + return true; + } + } return false; } + + /** + * Test code: this is disabled for production build. Sets + * this alarm to go off on the next minute + */ + void setTestAlarm() { + + // start with now + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTimeInMillis(System.currentTimeMillis()); + + int nowHour = c.get(java.util.Calendar.HOUR_OF_DAY); + int nowMinute = c.get(java.util.Calendar.MINUTE); + + int minutes = (nowMinute + 1) % 60; + int hour = nowHour + (nowMinute == 0? 1 : 0); + + saveAlarm(this, mId, true, hour, minutes, mDaysOfWeek, true, + mAlarmPref.mAlert.toString(), true); + } + } -- cgit v1.2.3 From e521e90d0fff0a53df54fcf9d83cc2772a73791f Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Thu, 22 Jan 2009 00:13:44 -0800 Subject: auto import from //branches/cupcake/...@127436 --- src/com/android/alarmclock/AlarmReceiver.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 70c24a2b5..24ab33cd6 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -54,10 +54,11 @@ public class AlarmReceiver extends BroadcastReceiver { AlarmKlaxon klaxon = AlarmKlaxon.getInstance(); klaxon.play(context, id); - /* launch UI */ + /* launch UI, explicitly stating that this is not due to user action + * so that the current app's notification management is not disturbed */ Intent fireAlarm = new Intent(context, AlarmAlert.class); fireAlarm.putExtra(Alarms.ID, id); - fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); context.startActivity(fireAlarm); } } -- cgit v1.2.3 From e9789453c908325d931c04dd6183a3b393187615 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 10 Feb 2009 15:44:04 -0800 Subject: auto import from //branches/cupcake/...@130745 --- src/com/android/alarmclock/AlarmClock.java | 16 +++++- src/com/android/alarmclock/AlarmReceiver.java | 4 ++ .../android/alarmclock/AnalogGadgetProvider.java | 66 ++++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/com/android/alarmclock/AnalogGadgetProvider.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index b068c4591..6230d16a4 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -158,8 +158,20 @@ public class AlarmClock extends Activity { }; @Override - public boolean onContextItemSelected(MenuItem item) { - Alarms.deleteAlarm(this, item.getItemId()); + public boolean onContextItemSelected(final MenuItem item) { + // Confirm that the alarm will be deleted. + new AlertDialog.Builder(this) + .setTitle(getString(R.string.delete_alarm)) + .setMessage(getString(R.string.delete_alarm_confirm)) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface d, int w) { + Alarms.deleteAlarm(AlarmClock.this, + item.getItemId()); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); return true; } diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 24ab33cd6..af18b7b7a 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -54,6 +54,10 @@ public class AlarmReceiver extends BroadcastReceiver { AlarmKlaxon klaxon = AlarmKlaxon.getInstance(); klaxon.play(context, id); + /* Close dialogs and window shade */ + Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.sendBroadcast(i); + /* launch UI, explicitly stating that this is not due to user action * so that the current app's notification management is not disturbed */ Intent fireAlarm = new Intent(context, AlarmAlert.class); diff --git a/src/com/android/alarmclock/AnalogGadgetProvider.java b/src/com/android/alarmclock/AnalogGadgetProvider.java new file mode 100644 index 000000000..1a6a62727 --- /dev/null +++ b/src/com/android/alarmclock/AnalogGadgetProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2009 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.alarmclock; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.database.Cursor; +import android.gadget.GadgetManager; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.provider.Calendar; +import android.provider.Calendar.Attendees; +import android.provider.Calendar.Calendars; +import android.provider.Calendar.EventsColumns; +import android.provider.Calendar.Instances; +import android.provider.Calendar.Reminders; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Config; +import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; + +import java.util.Arrays; + +/** + * Simple gadget to show analog clock. + */ +public class AnalogGadgetProvider extends BroadcastReceiver { + static final String TAG = "AnalogGadgetProvider"; + + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (GadgetManager.GADGET_UPDATE_ACTION.equals(action)) { + RemoteViews views = new RemoteViews(context.getPackageName(), + R.layout.analog_gadget); + + int[] gadgetIds = intent.getIntArrayExtra(GadgetManager.EXTRA_GADGET_IDS); + + GadgetManager gm = GadgetManager.getInstance(context); + gm.updateGadget(gadgetIds, views); + } + } +} + -- cgit v1.2.3 From 60c60b0b1c7faa502d581346c8a8deb9c338c85e Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Fri, 13 Feb 2009 12:57:52 -0800 Subject: auto import from //branches/cupcake/...@131421 --- src/com/android/alarmclock/AlarmAlert.java | 126 ++++++++++++--------- src/com/android/alarmclock/AlarmKlaxon.java | 79 ++++++++----- .../android/alarmclock/AnalogGadgetProvider.java | 2 +- 3 files changed, 123 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index c006d4d70..a9b27bcfd 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -21,6 +21,7 @@ import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.Bundle; import android.view.View; @@ -57,8 +58,6 @@ public class AlarmAlert extends Activity { fully debugged the app failing to start up */ Log.v("AlarmAlert.onCreate()"); - setContentView(R.layout.alarm_alert); - mKlaxon = AlarmKlaxon.getInstance(); // Popup alert over black screen @@ -77,18 +76,6 @@ public class AlarmAlert extends Activity { mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); - /* set clock face */ - LayoutInflater mFactory = LayoutInflater.from(this); - SharedPreferences settings = getSharedPreferences(AlarmClock.PREFERENCES, 0); - int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); - if (face < 0 || face >= AlarmClock.CLOCKS.length) face = 0; - View clockLayout = (View)mFactory.inflate(AlarmClock.CLOCKS[face], null); - ViewGroup clockView = (ViewGroup)findViewById(R.id.clockView); - clockView.addView(clockLayout); - if (clockLayout instanceof DigitalClock) { - ((DigitalClock)clockLayout).setAnimate(); - } - mAlarmId = getIntent().getIntExtra(Alarms.ID, -1); /* allow next alarm to trigger while this activity is @@ -97,33 +84,76 @@ public class AlarmAlert extends Activity { Alarms.disableAlert(AlarmAlert.this, mAlarmId); Alarms.setNextAlert(this); + mKlaxon.setKillerCallback(new AlarmKlaxon.KillerCallback() { + public void onKilled() { + if (Log.LOGV) Log.v("onKilled()"); + TextView silenced = (TextView)findViewById(R.id.silencedText); + silenced.setText( + getString(R.string.alarm_alert_alert_silenced, + AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60)); + silenced.setVisibility(View.VISIBLE); + + /* don't allow snooze */ + mSnoozeButton.setEnabled(false); + + mKlaxon.stop(AlarmAlert.this, mSnoozed); + releaseLocks(); + } + }); + + mKlaxon.restoreInstanceState(this, icicle); + + updateLayout(); + } + + private void updateLayout() { + setContentView(R.layout.alarm_alert); + + /* set clock face */ + LayoutInflater mFactory = LayoutInflater.from(this); + SharedPreferences settings = + getSharedPreferences(AlarmClock.PREFERENCES, 0); + int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); + if (face < 0 || face >= AlarmClock.CLOCKS.length) { + face = 0; + } + View clockLayout = + (View) mFactory.inflate(AlarmClock.CLOCKS[face], null); + ViewGroup clockView = (ViewGroup) findViewById(R.id.clockView); + clockView.addView(clockLayout); + if (clockLayout instanceof DigitalClock) { + ((DigitalClock) clockLayout).setAnimate(); + } + /* snooze behavior: pop a snooze confirmation view, kick alarm manager. */ mSnoozeButton = (Button) findViewById(R.id.snooze); mSnoozeButton.requestFocus(); mSnoozeButton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { - /* If next alarm is set for sooner than the snooze interval, - don't snooze: instead toast user that snooze will not be set */ - final long snoozeTarget = System.currentTimeMillis() + 1000 * 60 * SNOOZE_MINUTES; - long nextAlarm = Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); + // If next alarm is set for sooner than the snooze interval, + // don't snooze: instead toast user that snooze will not be set + final long snoozeTarget = System.currentTimeMillis() + + (1000 * 60 * SNOOZE_MINUTES); + final long nextAlarm = + Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); + String displayTime = null; if (nextAlarm < snoozeTarget) { Calendar c = Calendar.getInstance(); c.setTimeInMillis(nextAlarm); - Toast.makeText(AlarmAlert.this, - getString(R.string.alarm_alert_snooze_not_set, - Alarms.formatTime(AlarmAlert.this, c)), - Toast.LENGTH_LONG).show(); + displayTime = getString(R.string.alarm_alert_snooze_set, + Alarms.formatTime(AlarmAlert.this, c)); } else { - Toast.makeText(AlarmAlert.this, - getString(R.string.alarm_alert_snooze_set, - SNOOZE_MINUTES), - Toast.LENGTH_LONG).show(); - - Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTarget); + Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, + snoozeTarget); Alarms.setNextAlert(AlarmAlert.this); mSnoozed = true; + displayTime = getString(R.string.alarm_alert_snooze_set, + SNOOZE_MINUTES); } + // Display the snooze minutes in a toast. + Toast.makeText(AlarmAlert.this, displayTime, + Toast.LENGTH_LONG).show(); mKlaxon.stop(AlarmAlert.this, mSnoozed); releaseLocks(); finish(); @@ -131,32 +161,14 @@ public class AlarmAlert extends Activity { }); /* dismiss button: close notification */ - findViewById(R.id.dismiss).setOnClickListener(new Button.OnClickListener() { - public void onClick(View v) { - mKlaxon.stop(AlarmAlert.this, mSnoozed); - releaseLocks(); - finish(); - } - }); - - mKlaxon.setKillerCallback(new AlarmKlaxon.KillerCallback() { - public void onKilled() { - if (Log.LOGV) Log.v("onKilled()"); - TextView silenced = (TextView)findViewById(R.id.silencedText); - silenced.setText( - getString(R.string.alarm_alert_alert_silenced, - AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60)); - silenced.setVisibility(View.VISIBLE); - - /* don't allow snooze */ - mSnoozeButton.setEnabled(false); - - mKlaxon.stop(AlarmAlert.this, mSnoozed); - releaseLocks(); - } - }); - - mKlaxon.restoreInstanceState(this, icicle); + findViewById(R.id.dismiss).setOnClickListener( + new Button.OnClickListener() { + public void onClick(View v) { + mKlaxon.stop(AlarmAlert.this, mSnoozed); + releaseLocks(); + finish(); + } + }); } /** @@ -200,6 +212,12 @@ public class AlarmAlert extends Activity { mKlaxon.onSaveInstanceState(icicle); } + @Override + public void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + updateLayout(); + } + private synchronized void enableKeyguard() { if (mKeyguardLock != null) { mKeyguardLock.reenableKeyguard(); diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index 6ea7f9a12..c582da58f 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -18,6 +18,7 @@ package com.android.alarmclock; import android.content.ContentResolver; import android.content.Context; +import android.content.res.AssetFileDescriptor; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnErrorListener; @@ -91,44 +92,60 @@ class AlarmKlaxon implements Alarms.AlarmSettings { if (Log.LOGV) Log.v("AlarmKlaxon.play() " + mAlarmId + " alert " + mAlert); - if (mVibrate) { - mVibrator.vibrate(sVibratePattern, 0); - } else { - mVibrator.cancel(); - } - /* play audio alert */ if (mAlert == null) { Log.e("Unable to play alarm: no audio file available"); } else { - /* we need a new MediaPlayer when we change media URLs */ mMediaPlayer = new MediaPlayer(); - if (mMediaPlayer == null) { - Log.e("Unable to instantiate MediaPlayer"); - } else { - mMediaPlayer.setOnErrorListener(new OnErrorListener() { - public boolean onError(MediaPlayer mp, int what, int extra) { - Log.e("Error occurred while playing audio."); - mMediaPlayer.stop(); - mMediaPlayer.release(); - mMediaPlayer = null; - return true; - } - }); - - try { - mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mMediaPlayer.setLooping(true); - mMediaPlayer.prepare(); - } catch (Exception ex) { - Log.e("Error playing alarm: " + mAlert, ex); - return; + mMediaPlayer.setOnErrorListener(new OnErrorListener() { + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e("Error occurred while playing audio."); + mp.stop(); + mp.release(); + mMediaPlayer = null; + return true; } + }); + + try { + mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); + } catch (Exception ex) { + Log.v("Using the fallback ringtone"); + /* The alert may be on the sd card which could be busy right + * now. Use the fallback ringtone. */ + AssetFileDescriptor afd = + context.getResources().openRawResourceFd( + com.android.internal.R.raw.fallbackring); + if (afd != null) { + try { + mMediaPlayer.setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), afd.getLength()); + afd.close(); + } catch (Exception ex2) { + Log.e("Failed to play fallback ringtone", ex2); + /* At this point we just don't play anything */ + } + } + } + /* Now try to play the alert. */ + try { + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + mMediaPlayer.setLooping(true); + mMediaPlayer.prepare(); mMediaPlayer.start(); + } catch (Exception ex) { + Log.e("Error playing alarm: " + mAlert, ex); } } + + /* Start the vibrator after everything is ok with the media player */ + if (mVibrate) { + mVibrator.vibrate(sVibratePattern, 0); + } else { + mVibrator.cancel(); + } + enableKiller(); mPlaying = true; } @@ -144,7 +161,11 @@ class AlarmKlaxon implements Alarms.AlarmSettings { mPlaying = false; // Stop audio playing - if (mMediaPlayer != null) mMediaPlayer.stop(); + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } // Stop vibrator mVibrator.cancel(); diff --git a/src/com/android/alarmclock/AnalogGadgetProvider.java b/src/com/android/alarmclock/AnalogGadgetProvider.java index 1a6a62727..1143da586 100644 --- a/src/com/android/alarmclock/AnalogGadgetProvider.java +++ b/src/com/android/alarmclock/AnalogGadgetProvider.java @@ -52,7 +52,7 @@ public class AnalogGadgetProvider extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (GadgetManager.GADGET_UPDATE_ACTION.equals(action)) { + if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.analog_gadget); -- cgit v1.2.3 From 889cc95210a0d3867df01ac98c59426cc424bfbf Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Thu, 19 Feb 2009 10:57:35 -0800 Subject: auto import from //branches/cupcake/...@132276 --- src/com/android/alarmclock/AlarmAlert.java | 167 ++++++++++++++++++++-------- src/com/android/alarmclock/AlarmKlaxon.java | 23 ---- 2 files changed, 118 insertions(+), 72 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index a9b27bcfd..eefd5bb91 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -24,6 +24,7 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.Bundle; +import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater; @@ -40,12 +41,16 @@ import java.util.Calendar; */ public class AlarmAlert extends Activity { - private final static int SNOOZE_MINUTES = 10; + private static final int SNOOZE_MINUTES = 10; + private static final int UNKNOWN = 0; + private static final int SNOOZE = 1; + private static final int DISMISS = 2; + private static final int KILLED = 3; private KeyguardManager mKeyguardManager; private KeyguardManager.KeyguardLock mKeyguardLock = null; private Button mSnoozeButton; - private boolean mSnoozed; + private int mState = UNKNOWN; private AlarmKlaxon mKlaxon; private int mAlarmId; @@ -87,25 +92,29 @@ public class AlarmAlert extends Activity { mKlaxon.setKillerCallback(new AlarmKlaxon.KillerCallback() { public void onKilled() { if (Log.LOGV) Log.v("onKilled()"); - TextView silenced = (TextView)findViewById(R.id.silencedText); - silenced.setText( - getString(R.string.alarm_alert_alert_silenced, - AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60)); - silenced.setVisibility(View.VISIBLE); + updateSilencedText(); /* don't allow snooze */ mSnoozeButton.setEnabled(false); - mKlaxon.stop(AlarmAlert.this, mSnoozed); - releaseLocks(); + // Dismiss the alarm but mark the state as killed so if the + // config changes, we show the silenced message and disable + // snooze. + dismiss(); + mState = KILLED; } }); - mKlaxon.restoreInstanceState(this, icicle); - updateLayout(); } + private void updateSilencedText() { + TextView silenced = (TextView) findViewById(R.id.silencedText); + silenced.setText(getString(R.string.alarm_alert_alert_silenced, + AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60)); + silenced.setVisibility(View.VISIBLE); + } + private void updateLayout() { setContentView(R.layout.alarm_alert); @@ -129,48 +138,73 @@ public class AlarmAlert extends Activity { manager. */ mSnoozeButton = (Button) findViewById(R.id.snooze); mSnoozeButton.requestFocus(); - mSnoozeButton.setOnClickListener(new Button.OnClickListener() { - public void onClick(View v) { - // If next alarm is set for sooner than the snooze interval, - // don't snooze: instead toast user that snooze will not be set - final long snoozeTarget = System.currentTimeMillis() - + (1000 * 60 * SNOOZE_MINUTES); - final long nextAlarm = - Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); - String displayTime = null; - if (nextAlarm < snoozeTarget) { - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(nextAlarm); - displayTime = getString(R.string.alarm_alert_snooze_set, - Alarms.formatTime(AlarmAlert.this, c)); - } else { - Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, - snoozeTarget); - Alarms.setNextAlert(AlarmAlert.this); - mSnoozed = true; - displayTime = getString(R.string.alarm_alert_snooze_set, - SNOOZE_MINUTES); + // If this was a configuration change, keep the silenced text if the + // alarm was killed. + if (mState == KILLED) { + updateSilencedText(); + mSnoozeButton.setEnabled(false); + } else { + mSnoozeButton.setOnClickListener(new Button.OnClickListener() { + public void onClick(View v) { + snooze(); + finish(); } - // Display the snooze minutes in a toast. - Toast.makeText(AlarmAlert.this, displayTime, - Toast.LENGTH_LONG).show(); - mKlaxon.stop(AlarmAlert.this, mSnoozed); - releaseLocks(); - finish(); - } - }); + }); + } /* dismiss button: close notification */ findViewById(R.id.dismiss).setOnClickListener( new Button.OnClickListener() { public void onClick(View v) { - mKlaxon.stop(AlarmAlert.this, mSnoozed); - releaseLocks(); + dismiss(); finish(); } }); } + // Attempt to snooze this alert. + private void snooze() { + if (mState != UNKNOWN) { + return; + } + // If the next alarm is set for sooner than the snooze interval, don't + // snooze. Instead, toast the user that the snooze will not be set. + final long snoozeTime = System.currentTimeMillis() + + (1000 * 60 * SNOOZE_MINUTES); + final long nextAlarm = + Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); + String displayTime = null; + if (nextAlarm < snoozeTime) { + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(nextAlarm); + displayTime = getString(R.string.alarm_alert_snooze_not_set, + Alarms.formatTime(AlarmAlert.this, c)); + mState = DISMISS; + } else { + Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTime); + Alarms.setNextAlert(AlarmAlert.this); + displayTime = getString(R.string.alarm_alert_snooze_set, + SNOOZE_MINUTES); + mState = SNOOZE; + } + // Intentionally log the snooze time for debugging. + Log.v(displayTime); + // Display the snooze minutes in a toast. + Toast.makeText(AlarmAlert.this, displayTime, Toast.LENGTH_LONG).show(); + mKlaxon.stop(this, mState == SNOOZE); + releaseLocks(); + } + + // Dismiss the alarm. + private void dismiss() { + if (mState != UNKNOWN) { + return; + } + mState = DISMISS; + mKlaxon.stop(this, false); + releaseLocks(); + } + /** * this is called when a second alarm is triggered while a * previous alert window is still active. @@ -179,6 +213,7 @@ public class AlarmAlert extends Activity { protected void onNewIntent(Intent intent) { super.onNewIntent(intent); if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); + mState = UNKNOWN; mSnoozeButton.setEnabled(true); disableKeyguard(); @@ -203,13 +238,8 @@ public class AlarmAlert extends Activity { protected void onStop() { super.onStop(); if (Log.LOGV) Log.v("AlarmAlert.onStop()"); - mKlaxon.stop(this, mSnoozed); - releaseLocks(); - } - - @Override - protected void onSaveInstanceState(Bundle icicle) { - mKlaxon.onSaveInstanceState(icicle); + // As a last resort, try to snooze if this activity is stopped. + snooze(); } @Override @@ -218,6 +248,45 @@ public class AlarmAlert extends Activity { updateLayout(); } + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Do this on key down to handle a few of the system keys. Only handle + // the snooze and dismiss this alert if the state is unknown. + boolean up = event.getAction() == KeyEvent.ACTION_UP; + boolean dismiss = false; + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + // Ignore ENDCALL because we do not receive the event if the screen + // is on. However, we do receive the key up for ENDCALL if the + // screen was off. + case KeyEvent.KEYCODE_ENDCALL: + break; + // Volume keys dismiss the alarm + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + dismiss = true; + // All other keys will snooze the alarm + default: + // Check for UNKNOWN here so that we intercept both key events + // and prevent the volume keys from triggering their default + // behavior. + if (mState == UNKNOWN && up) { + if (dismiss) { + dismiss(); + } else { + snooze(); + } + finish(); + } + return true; + } + return super.dispatchKeyEvent(event); + } + private synchronized void enableKeyguard() { if (mKeyguardLock != null) { mKeyguardLock.reenableKeyguard(); diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index c582da58f..c862292dc 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -39,8 +39,6 @@ class AlarmKlaxon implements Alarms.AlarmSettings { /** Play alarm up to 10 minutes before silencing */ final static int ALARM_TIMEOUT_SECONDS = 10 * 60; - final static String ICICLE_PLAYING = "IciclePlaying"; - final static String ICICLE_ALARMID = "IcicleAlarmId"; private static long[] sVibratePattern = new long[] { 500, 500 }; @@ -186,27 +184,6 @@ class AlarmKlaxon implements Alarms.AlarmSettings { mKillerCallback = killerCallback; } - - /** - * Called by the AlarmAlert activity on configuration change - */ - protected void onSaveInstanceState(Bundle icicle) { - icicle.putBoolean(ICICLE_PLAYING, mPlaying); - icicle.putInt(ICICLE_ALARMID, mAlarmId); - } - - /** - * Restores alarm playback state on configuration change - */ - void restoreInstanceState(Context context, Bundle icicle) { - if (!mPlaying && - icicle != null && - icicle.containsKey(ICICLE_PLAYING) && - icicle.getBoolean(ICICLE_PLAYING)) { - play(context, icicle.getInt(ICICLE_ALARMID)); - } - } - /** * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm * won't run all day. -- cgit v1.2.3 From 8d7704fad6d9e52865b1789948e20e2f8b200823 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 18:28:49 -0800 Subject: auto import from //depot/cupcake/@135843 --- src/com/android/alarmclock/AlarmAlert.java | 311 --------- src/com/android/alarmclock/AlarmAlertWakeLock.java | 51 -- src/com/android/alarmclock/AlarmClock.java | 314 --------- src/com/android/alarmclock/AlarmInitReceiver.java | 44 -- src/com/android/alarmclock/AlarmKlaxon.java | 212 ------ src/com/android/alarmclock/AlarmPreference.java | 54 -- src/com/android/alarmclock/AlarmProvider.java | 237 ------- src/com/android/alarmclock/AlarmReceiver.java | 68 -- src/com/android/alarmclock/Alarms.java | 758 --------------------- .../android/alarmclock/AnalogGadgetProvider.java | 66 -- src/com/android/alarmclock/ClockPicker.java | 119 ---- src/com/android/alarmclock/DigitalClock.java | 209 ------ src/com/android/alarmclock/Log.java | 42 -- src/com/android/alarmclock/RepeatPreference.java | 76 --- src/com/android/alarmclock/SetAlarm.java | 398 ----------- src/com/android/alarmclock/ToastMaster.java | 41 -- 16 files changed, 3000 deletions(-) delete mode 100644 src/com/android/alarmclock/AlarmAlert.java delete mode 100644 src/com/android/alarmclock/AlarmAlertWakeLock.java delete mode 100644 src/com/android/alarmclock/AlarmClock.java delete mode 100644 src/com/android/alarmclock/AlarmInitReceiver.java delete mode 100644 src/com/android/alarmclock/AlarmKlaxon.java delete mode 100644 src/com/android/alarmclock/AlarmPreference.java delete mode 100644 src/com/android/alarmclock/AlarmProvider.java delete mode 100644 src/com/android/alarmclock/AlarmReceiver.java delete mode 100644 src/com/android/alarmclock/Alarms.java delete mode 100644 src/com/android/alarmclock/AnalogGadgetProvider.java delete mode 100644 src/com/android/alarmclock/ClockPicker.java delete mode 100644 src/com/android/alarmclock/DigitalClock.java delete mode 100644 src/com/android/alarmclock/Log.java delete mode 100644 src/com/android/alarmclock/RepeatPreference.java delete mode 100644 src/com/android/alarmclock/SetAlarm.java delete mode 100644 src/com/android/alarmclock/ToastMaster.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java deleted file mode 100644 index eefd5bb91..000000000 --- a/src/com/android/alarmclock/AlarmAlert.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.Activity; -import android.app.KeyguardManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.LayoutInflater; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.Toast; -import android.widget.TextView; - -import java.util.Calendar; - -/** - * Alarm Clock alarm alert: pops visible indicator and plays alarm - * tone - */ -public class AlarmAlert extends Activity { - - private static final int SNOOZE_MINUTES = 10; - private static final int UNKNOWN = 0; - private static final int SNOOZE = 1; - private static final int DISMISS = 2; - private static final int KILLED = 3; - - private KeyguardManager mKeyguardManager; - private KeyguardManager.KeyguardLock mKeyguardLock = null; - private Button mSnoozeButton; - private int mState = UNKNOWN; - - private AlarmKlaxon mKlaxon; - private int mAlarmId; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - /* FIXME Intentionally verbose: always log this until we've - fully debugged the app failing to start up */ - Log.v("AlarmAlert.onCreate()"); - - mKlaxon = AlarmKlaxon.getInstance(); - - // Popup alert over black screen - WindowManager.LayoutParams lp = getWindow().getAttributes(); - lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; - lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; - // XXX DO NOT COPY THIS!!! THIS IS BOGUS! Making an activity have - // a system alert type is completely broken, because the activity - // manager will still hide/show it as if it is part of the normal - // activity stack. If this is really what you want and you want it - // to work correctly, you should create and show your own custom window. - lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; - lp.token = null; - getWindow().setAttributes(lp); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - - mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); - - mAlarmId = getIntent().getIntExtra(Alarms.ID, -1); - - /* allow next alarm to trigger while this activity is - active */ - Alarms.disableSnoozeAlert(AlarmAlert.this); - Alarms.disableAlert(AlarmAlert.this, mAlarmId); - Alarms.setNextAlert(this); - - mKlaxon.setKillerCallback(new AlarmKlaxon.KillerCallback() { - public void onKilled() { - if (Log.LOGV) Log.v("onKilled()"); - updateSilencedText(); - - /* don't allow snooze */ - mSnoozeButton.setEnabled(false); - - // Dismiss the alarm but mark the state as killed so if the - // config changes, we show the silenced message and disable - // snooze. - dismiss(); - mState = KILLED; - } - }); - - updateLayout(); - } - - private void updateSilencedText() { - TextView silenced = (TextView) findViewById(R.id.silencedText); - silenced.setText(getString(R.string.alarm_alert_alert_silenced, - AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60)); - silenced.setVisibility(View.VISIBLE); - } - - private void updateLayout() { - setContentView(R.layout.alarm_alert); - - /* set clock face */ - LayoutInflater mFactory = LayoutInflater.from(this); - SharedPreferences settings = - getSharedPreferences(AlarmClock.PREFERENCES, 0); - int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); - if (face < 0 || face >= AlarmClock.CLOCKS.length) { - face = 0; - } - View clockLayout = - (View) mFactory.inflate(AlarmClock.CLOCKS[face], null); - ViewGroup clockView = (ViewGroup) findViewById(R.id.clockView); - clockView.addView(clockLayout); - if (clockLayout instanceof DigitalClock) { - ((DigitalClock) clockLayout).setAnimate(); - } - - /* snooze behavior: pop a snooze confirmation view, kick alarm - manager. */ - mSnoozeButton = (Button) findViewById(R.id.snooze); - mSnoozeButton.requestFocus(); - // If this was a configuration change, keep the silenced text if the - // alarm was killed. - if (mState == KILLED) { - updateSilencedText(); - mSnoozeButton.setEnabled(false); - } else { - mSnoozeButton.setOnClickListener(new Button.OnClickListener() { - public void onClick(View v) { - snooze(); - finish(); - } - }); - } - - /* dismiss button: close notification */ - findViewById(R.id.dismiss).setOnClickListener( - new Button.OnClickListener() { - public void onClick(View v) { - dismiss(); - finish(); - } - }); - } - - // Attempt to snooze this alert. - private void snooze() { - if (mState != UNKNOWN) { - return; - } - // If the next alarm is set for sooner than the snooze interval, don't - // snooze. Instead, toast the user that the snooze will not be set. - final long snoozeTime = System.currentTimeMillis() - + (1000 * 60 * SNOOZE_MINUTES); - final long nextAlarm = - Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); - String displayTime = null; - if (nextAlarm < snoozeTime) { - final Calendar c = Calendar.getInstance(); - c.setTimeInMillis(nextAlarm); - displayTime = getString(R.string.alarm_alert_snooze_not_set, - Alarms.formatTime(AlarmAlert.this, c)); - mState = DISMISS; - } else { - Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTime); - Alarms.setNextAlert(AlarmAlert.this); - displayTime = getString(R.string.alarm_alert_snooze_set, - SNOOZE_MINUTES); - mState = SNOOZE; - } - // Intentionally log the snooze time for debugging. - Log.v(displayTime); - // Display the snooze minutes in a toast. - Toast.makeText(AlarmAlert.this, displayTime, Toast.LENGTH_LONG).show(); - mKlaxon.stop(this, mState == SNOOZE); - releaseLocks(); - } - - // Dismiss the alarm. - private void dismiss() { - if (mState != UNKNOWN) { - return; - } - mState = DISMISS; - mKlaxon.stop(this, false); - releaseLocks(); - } - - /** - * this is called when a second alarm is triggered while a - * previous alert window is still active. - */ - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); - mState = UNKNOWN; - mSnoozeButton.setEnabled(true); - disableKeyguard(); - - mAlarmId = intent.getIntExtra(Alarms.ID, -1); - - /* unset silenced message */ - TextView silenced = (TextView)findViewById(R.id.silencedText); - silenced.setVisibility(View.GONE); - - Alarms.setNextAlert(this); - setIntent(intent); - } - - @Override - protected void onResume() { - super.onResume(); - if (Log.LOGV) Log.v("AlarmAlert.onResume()"); - disableKeyguard(); - } - - @Override - protected void onStop() { - super.onStop(); - if (Log.LOGV) Log.v("AlarmAlert.onStop()"); - // As a last resort, try to snooze if this activity is stopped. - snooze(); - } - - @Override - public void onConfigurationChanged(Configuration config) { - super.onConfigurationChanged(config); - updateLayout(); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - // Do this on key down to handle a few of the system keys. Only handle - // the snooze and dismiss this alert if the state is unknown. - boolean up = event.getAction() == KeyEvent.ACTION_UP; - boolean dismiss = false; - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_CENTER: - // Ignore ENDCALL because we do not receive the event if the screen - // is on. However, we do receive the key up for ENDCALL if the - // screen was off. - case KeyEvent.KEYCODE_ENDCALL: - break; - // Volume keys dismiss the alarm - case KeyEvent.KEYCODE_VOLUME_UP: - case KeyEvent.KEYCODE_VOLUME_DOWN: - dismiss = true; - // All other keys will snooze the alarm - default: - // Check for UNKNOWN here so that we intercept both key events - // and prevent the volume keys from triggering their default - // behavior. - if (mState == UNKNOWN && up) { - if (dismiss) { - dismiss(); - } else { - snooze(); - } - finish(); - } - return true; - } - return super.dispatchKeyEvent(event); - } - - private synchronized void enableKeyguard() { - if (mKeyguardLock != null) { - mKeyguardLock.reenableKeyguard(); - mKeyguardLock = null; - } - } - - private synchronized void disableKeyguard() { - if (mKeyguardLock == null) { - mKeyguardLock = mKeyguardManager.newKeyguardLock(Log.LOGTAG); - mKeyguardLock.disableKeyguard(); - } - } - - /** - * release wake and keyguard locks - */ - private synchronized void releaseLocks() { - AlarmAlertWakeLock.release(); - enableKeyguard(); - } -} diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java deleted file mode 100644 index 083996e58..000000000 --- a/src/com/android/alarmclock/AlarmAlertWakeLock.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.alarmclock; - -import android.content.Context; -import android.os.PowerManager; - -/** - * Hold a wakelock that can be acquired in the AlarmReceiver and - * released in the AlarmAlert activity - */ -class AlarmAlertWakeLock { - - private static PowerManager.WakeLock sWakeLock; - - static void acquire(Context context) { - if (sWakeLock != null) { - sWakeLock.release(); - } - - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - - sWakeLock = pm.newWakeLock( - PowerManager.FULL_WAKE_LOCK | - PowerManager.ACQUIRE_CAUSES_WAKEUP | - PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); - sWakeLock.acquire(); - } - - static void release() { - if (Log.LOGV) Log.v("AlarmAlertWakeLock release"); - if (sWakeLock != null) { - sWakeLock.release(); - sWakeLock = null; - } - } -} diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java deleted file mode 100644 index 6230d16a4..000000000 --- a/src/com/android/alarmclock/AlarmClock.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.provider.Settings; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.CheckBox; - -import java.util.Calendar; - -/** - * AlarmClock application. - */ -public class AlarmClock extends Activity { - - final static String PREFERENCES = "AlarmClock"; - final static int SET_ALARM = 1; - final static String PREF_CLOCK_FACE = "face"; - final static String PREF_SHOW_CLOCK = "show_clock"; - - /** Cap alarm count at this number */ - final static int MAX_ALARM_COUNT = 12; - - /** This must be false for production. If true, turns on logging, - test code, etc. */ - final static boolean DEBUG = false; - - private SharedPreferences mPrefs; - private LayoutInflater mFactory; - private ViewGroup mClockLayout; - private View mClock = null; - private MenuItem mAddAlarmItem; - private MenuItem mToggleClockItem; - private ListView mAlarmsList; - private Cursor mCursor; - - /** - * Which clock face to show - */ - private int mFace = -1; - - /* - * FIXME: it would be nice for this to live in an xml config file. - */ - final static int[] CLOCKS = { - R.layout.clock_basic_bw, - R.layout.clock_googly, - R.layout.clock_droid2, - R.layout.clock_droids, - R.layout.digital_clock - }; - - private class AlarmTimeAdapter extends CursorAdapter { - public AlarmTimeAdapter(Context context, Cursor cursor) { - super(context, cursor); - } - - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View ret = mFactory.inflate(R.layout.alarm_time, parent, false); - DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); - digitalClock.setLive(false); - if (Log.LOGV) Log.v("newView " + cursor.getPosition()); - return ret; - } - - public void bindView(View view, Context context, Cursor cursor) { - final int id = cursor.getInt(Alarms.AlarmColumns.ALARM_ID_INDEX); - final int hour = cursor.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX); - final int minutes = cursor.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX); - final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek( - cursor.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); - final boolean enabled = cursor.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1; - - CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton); - onButton.setChecked(enabled); - onButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - boolean isChecked = ((CheckBox) v).isChecked(); - Alarms.enableAlarm(AlarmClock.this, id, isChecked); - if (isChecked) { - SetAlarm.popAlarmSetToast( - AlarmClock.this, hour, minutes, daysOfWeek); - } - } - }); - - DigitalClock digitalClock = (DigitalClock)view.findViewById(R.id.digitalClock); - if (Log.LOGV) Log.v("bindView " + cursor.getPosition() + " " + id + " " + hour + - ":" + minutes + " " + daysOfWeek.toString(context, true) + " dc " + digitalClock); - - digitalClock.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - if (true) { - Intent intent = new Intent(AlarmClock.this, SetAlarm.class); - intent.putExtra(Alarms.ID, id); - startActivityForResult(intent, SET_ALARM); - } else { - // TESTING: immediately pop alarm - Intent fireAlarm = new Intent(AlarmClock.this, AlarmAlert.class); - fireAlarm.putExtra(Alarms.ID, id); - fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(fireAlarm); - } - } - }); - - // set the alarm text - final Calendar c = Calendar.getInstance(); - c.set(Calendar.HOUR_OF_DAY, hour); - c.set(Calendar.MINUTE, minutes); - digitalClock.updateTime(c); - TextView daysOfWeekView = (TextView) digitalClock.findViewById(R.id.daysOfWeek); - daysOfWeekView.setText(daysOfWeek.toString(AlarmClock.this, false)); - - // Build context menu - digitalClock.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { - public void onCreateContextMenu(ContextMenu menu, View view, - ContextMenuInfo menuInfo) { - menu.setHeaderTitle(Alarms.formatTime(AlarmClock.this, c)); - MenuItem deleteAlarmItem = menu.add(0, id, 0, R.string.delete_alarm); - } - }); - } - }; - - @Override - public boolean onContextItemSelected(final MenuItem item) { - // Confirm that the alarm will be deleted. - new AlertDialog.Builder(this) - .setTitle(getString(R.string.delete_alarm)) - .setMessage(getString(R.string.delete_alarm_confirm)) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface d, int w) { - Alarms.deleteAlarm(AlarmClock.this, - item.getItemId()); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .show(); - return true; - } - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - // sanity check -- no database, no clock - if (getContentResolver() == null) { - new AlertDialog.Builder(this) - .setTitle(getString(R.string.error)) - .setMessage(getString(R.string.dberror)) - .setPositiveButton( - android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }) - .setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - finish(); - }}) - .setIcon(android.R.drawable.ic_dialog_alert) - .create().show(); - return; - } - - setContentView(R.layout.alarm_clock); - mFactory = LayoutInflater.from(this); - mPrefs = getSharedPreferences(PREFERENCES, 0); - - mCursor = Alarms.getAlarmsCursor(getContentResolver()); - mAlarmsList = (ListView) findViewById(R.id.alarms_list); - mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); - mAlarmsList.setVerticalScrollBarEnabled(true); - mAlarmsList.setItemsCanFocus(true); - - mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); - mClockLayout.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - final Intent intent = new Intent(AlarmClock.this, ClockPicker.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - }); - - setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); - } - - @Override - protected void onResume() { - super.onResume(); - - int face = mPrefs.getInt(PREF_CLOCK_FACE, 0); - if (mFace != face) { - if (face < 0 || face >= AlarmClock.CLOCKS.length) - mFace = 0; - else - mFace = face; - inflateClock(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - ToastMaster.cancelToast(); - mCursor.deactivate(); - } - - protected void inflateClock() { - if (mClock != null) { - mClockLayout.removeView(mClock); - } - mClock = mFactory.inflate(CLOCKS[mFace], null); - mClockLayout.addView(mClock, 0); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - - mAddAlarmItem = menu.add(0, 0, 0, R.string.add_alarm); - mAddAlarmItem.setIcon(android.R.drawable.ic_menu_add); - - mToggleClockItem = menu.add(0, 0, 0, R.string.hide_clock); - mToggleClockItem.setIcon(R.drawable.ic_menu_clock_face); - - return true; - } - - /** - * Only allow user to add a new alarm if there are fewer than - * MAX_ALARM_COUNT - */ - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - mAddAlarmItem.setVisible(mAlarmsList.getChildCount() < MAX_ALARM_COUNT); - mToggleClockItem.setTitle(getClockVisibility() ? R.string.hide_clock : - R.string.show_clock); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item == mAddAlarmItem) { - Uri uri = Alarms.addAlarm(getContentResolver()); - // FIXME: scroll to new item. mAlarmsList.requestChildRectangleOnScreen() ? - String segment = uri.getPathSegments().get(1); - int newId = Integer.parseInt(segment); - if (Log.LOGV) Log.v("In AlarmClock, new alarm id = " + newId); - Intent intent = new Intent(AlarmClock.this, SetAlarm.class); - intent.putExtra(Alarms.ID, newId); - startActivityForResult(intent, SET_ALARM); - return true; - } else if (item == mToggleClockItem) { - setClockVisibility(!getClockVisibility()); - saveClockVisibility(); - return true; - } - - return false; - } - - - private boolean getClockVisibility() { - return mClockLayout.getVisibility() == View.VISIBLE; - } - - private void setClockVisibility(boolean visible) { - mClockLayout.setVisibility(visible ? View.VISIBLE : View.GONE); - } - - private void saveClockVisibility() { - mPrefs.edit().putBoolean(PREF_SHOW_CLOCK, getClockVisibility()).commit(); - } -} diff --git a/src/com/android/alarmclock/AlarmInitReceiver.java b/src/com/android/alarmclock/AlarmInitReceiver.java deleted file mode 100644 index 77549b07a..000000000 --- a/src/com/android/alarmclock/AlarmInitReceiver.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; - -public class AlarmInitReceiver extends BroadcastReceiver { - - /** - * Sets alarm on ACTION_BOOT_COMPLETED. Resets alarm on - * TIME_SET, TIMEZONE_CHANGED - */ - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Log.LOGV) Log.v("AlarmInitReceiver" + action); - - if (context.getContentResolver() == null) { - Log.e("AlarmInitReceiver: FAILURE unable to get content resolver. Alarms inactive."); - return; - } - if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - Alarms.disableSnoozeAlert(context); - Alarms.disableExpiredAlarms(context); - } - Alarms.setNextAlert(context); - } -} diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java deleted file mode 100644 index c862292dc..000000000 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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.alarmclock; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnErrorListener; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Vibrator; - -/** - * Manages alarms and vibe. Singleton, so it can be initiated in - * AlarmReceiver and shut down in the AlarmAlert activity - */ -class AlarmKlaxon implements Alarms.AlarmSettings { - - interface KillerCallback { - public void onKilled(); - } - - /** Play alarm up to 10 minutes before silencing */ - final static int ALARM_TIMEOUT_SECONDS = 10 * 60; - - private static long[] sVibratePattern = new long[] { 500, 500 }; - - private static AlarmKlaxon sInstance; - - private int mAlarmId; - private String mAlert; - private Alarms.DaysOfWeek mDaysOfWeek; - private boolean mVibrate; - - private boolean mPlaying = false; - - private Vibrator mVibrator; - private MediaPlayer mMediaPlayer; - - private Handler mTimeout; - private KillerCallback mKillerCallback; - - - static synchronized AlarmKlaxon getInstance() { - if (sInstance == null) sInstance = new AlarmKlaxon(); - return sInstance; - } - - private AlarmKlaxon() { - mVibrator = new Vibrator(); - } - - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - if (Log.LOGV) Log.v("AlarmKlaxon.reportAlarm: " + idx + " " + hour + - " " + minutes + " dow " + daysOfWeek); - mAlert = alert; - mDaysOfWeek = daysOfWeek; - mVibrate = vibrate; - } - - synchronized void play(Context context, int alarmId) { - ContentResolver contentResolver = context.getContentResolver(); - - if (mPlaying) stop(context, false); - - mAlarmId = alarmId; - - /* this will call reportAlarm() callback */ - Alarms.getAlarm(contentResolver, this, mAlarmId); - - if (Log.LOGV) Log.v("AlarmKlaxon.play() " + mAlarmId + " alert " + mAlert); - - /* play audio alert */ - if (mAlert == null) { - Log.e("Unable to play alarm: no audio file available"); - } else { - /* we need a new MediaPlayer when we change media URLs */ - mMediaPlayer = new MediaPlayer(); - mMediaPlayer.setOnErrorListener(new OnErrorListener() { - public boolean onError(MediaPlayer mp, int what, int extra) { - Log.e("Error occurred while playing audio."); - mp.stop(); - mp.release(); - mMediaPlayer = null; - return true; - } - }); - - try { - mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); - } catch (Exception ex) { - Log.v("Using the fallback ringtone"); - /* The alert may be on the sd card which could be busy right - * now. Use the fallback ringtone. */ - AssetFileDescriptor afd = - context.getResources().openRawResourceFd( - com.android.internal.R.raw.fallbackring); - if (afd != null) { - try { - mMediaPlayer.setDataSource(afd.getFileDescriptor(), - afd.getStartOffset(), afd.getLength()); - afd.close(); - } catch (Exception ex2) { - Log.e("Failed to play fallback ringtone", ex2); - /* At this point we just don't play anything */ - } - } - } - /* Now try to play the alert. */ - try { - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mMediaPlayer.setLooping(true); - mMediaPlayer.prepare(); - mMediaPlayer.start(); - } catch (Exception ex) { - Log.e("Error playing alarm: " + mAlert, ex); - } - } - - /* Start the vibrator after everything is ok with the media player */ - if (mVibrate) { - mVibrator.vibrate(sVibratePattern, 0); - } else { - mVibrator.cancel(); - } - - enableKiller(); - mPlaying = true; - } - - - /** - * Stops alarm audio and disables alarm if it not snoozed and not - * repeating - */ - synchronized void stop(Context context, boolean snoozed) { - if (Log.LOGV) Log.v("AlarmKlaxon.stop() " + mAlarmId); - if (mPlaying) { - mPlaying = false; - - // Stop audio playing - if (mMediaPlayer != null) { - mMediaPlayer.stop(); - mMediaPlayer.release(); - mMediaPlayer = null; - } - - // Stop vibrator - mVibrator.cancel(); - - /* disable alarm only if it is not set to repeat */ - if (!snoozed && ((mDaysOfWeek == null || !mDaysOfWeek.isRepeatSet()))) { - Alarms.enableAlarm(context, mAlarmId, false); - } - } - disableKiller(); - } - - /** - * This callback called when alarm killer times out unattended - * alarm - */ - void setKillerCallback(KillerCallback killerCallback) { - mKillerCallback = killerCallback; - } - - /** - * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm - * won't run all day. - * - * This just cancels the audio, but leaves the notification - * popped, so the user will know that the alarm tripped. - */ - private void enableKiller() { - mTimeout = new Handler(); - mTimeout.postDelayed(new Runnable() { - public void run() { - if (Log.LOGV) Log.v("*********** Alarm killer triggered *************"); - if (mKillerCallback != null) mKillerCallback.onKilled(); - } - }, 1000 * ALARM_TIMEOUT_SECONDS); - } - - private void disableKiller() { - if (mTimeout != null) { - mTimeout.removeCallbacksAndMessages(null); - mTimeout = null; - } - } - - -} diff --git a/src/com/android/alarmclock/AlarmPreference.java b/src/com/android/alarmclock/AlarmPreference.java deleted file mode 100644 index 0fd4f89dd..000000000 --- a/src/com/android/alarmclock/AlarmPreference.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.alarmclock; - -import android.content.Context; -import android.net.Uri; -import android.preference.RingtonePreference; -import android.util.AttributeSet; - -public class AlarmPreference extends RingtonePreference { - public Uri mAlert; - private IRingtoneChangedListener mRingtoneChangedListener; - - public interface IRingtoneChangedListener { - public void onRingtoneChanged(Uri ringtoneUri); - }; - - public AlarmPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void setRingtoneChangedListener(IRingtoneChangedListener listener) { - mRingtoneChangedListener = listener; - } - - @Override - protected void onSaveRingtone(Uri ringtoneUri) { - if (ringtoneUri != null) { - mAlert = ringtoneUri; - if (mRingtoneChangedListener != null) { - mRingtoneChangedListener.onRingtoneChanged(ringtoneUri); - } - } - } - - @Override - protected Uri onRestoreRingtone() { - return mAlert; - } -} diff --git a/src/com/android/alarmclock/AlarmProvider.java b/src/com/android/alarmclock/AlarmProvider.java deleted file mode 100644 index 74fdd2e89..000000000 --- a/src/com/android/alarmclock/AlarmProvider.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.text.TextUtils; - -public class AlarmProvider extends ContentProvider { - private SQLiteOpenHelper mOpenHelper; - - private static final int ALARMS = 1; - private static final int ALARMS_ID = 2; - private static final UriMatcher sURLMatcher = new UriMatcher( - UriMatcher.NO_MATCH); - - static { - sURLMatcher.addURI("com.android.alarmclock", "alarm", ALARMS); - sURLMatcher.addURI("com.android.alarmclock", "alarm/#", ALARMS_ID); - } - - private static class DatabaseHelper extends SQLiteOpenHelper { - private static final String DATABASE_NAME = "alarms.db"; - private static final int DATABASE_VERSION = 5; - - public DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE alarms (" + - "_id INTEGER PRIMARY KEY," + - "hour INTEGER, " + - "minutes INTEGER, " + - "daysofweek INTEGER, " + - "alarmtime INTEGER, " + - "enabled INTEGER, " + - "vibrate INTEGER, " + - "message TEXT, " + - "alert TEXT);"); - - // insert default alarms - String insertMe = "INSERT INTO alarms " + - "(hour, minutes, daysofweek, alarmtime, enabled, vibrate, message, alert) " + - "VALUES "; - db.execSQL(insertMe + "(7, 0, 127, 0, 0, 1, '', '');"); - db.execSQL(insertMe + "(8, 30, 31, 0, 0, 1, '', '');"); - db.execSQL(insertMe + "(9, 00, 0, 0, 0, 1, '', '');"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { - if (Log.LOGV) Log.v( - "Upgrading alarms database from version " + - oldVersion + " to " + currentVersion + - ", which will destroy all old data"); - db.execSQL("DROP TABLE IF EXISTS alarms"); - onCreate(db); - } - } - - public AlarmProvider() { - } - - @Override - public boolean onCreate() { - mOpenHelper = new DatabaseHelper(getContext()); - return true; - } - - @Override - public Cursor query(Uri url, String[] projectionIn, String selection, - String[] selectionArgs, String sort) { - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - - // Generate the body of the query - int match = sURLMatcher.match(url); - switch (match) { - case ALARMS: - qb.setTables("alarms"); - break; - case ALARMS_ID: - qb.setTables("alarms"); - qb.appendWhere("_id="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - default: - throw new IllegalArgumentException("Unknown URL " + url); - } - - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, - null, null, sort); - - if (ret == null) { - if (Log.LOGV) Log.v("Alarms.query: failed"); - } else { - ret.setNotificationUri(getContext().getContentResolver(), url); - } - - return ret; - } - - @Override - public String getType(Uri url) { - int match = sURLMatcher.match(url); - switch (match) { - case ALARMS: - return "vnd.android.cursor.dir/alarms"; - case ALARMS_ID: - return "vnd.android.cursor.item/alarms"; - default: - throw new IllegalArgumentException("Unknown URL"); - } - } - - @Override - public int update(Uri url, ContentValues values, String where, String[] whereArgs) { - int count; - long rowId = 0; - int match = sURLMatcher.match(url); - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - switch (match) { - case ALARMS_ID: { - String segment = url.getPathSegments().get(1); - rowId = Long.parseLong(segment); - count = db.update("alarms", values, "_id=" + rowId, null); - break; - } - default: { - throw new UnsupportedOperationException( - "Cannot update URL: " + url); - } - } - if (Log.LOGV) Log.v("*** notifyChange() rowId: " + rowId + " url " + url); - getContext().getContentResolver().notifyChange(url, null); - return count; - } - - @Override - public Uri insert(Uri url, ContentValues initialValues) { - if (sURLMatcher.match(url) != ALARMS) { - throw new IllegalArgumentException("Cannot insert into URL: " + url); - } - - ContentValues values; - if (initialValues != null) - values = new ContentValues(initialValues); - else - values = new ContentValues(); - - if (!values.containsKey(Alarms.AlarmColumns.HOUR)) - values.put(Alarms.AlarmColumns.HOUR, 0); - - if (!values.containsKey(Alarms.AlarmColumns.MINUTES)) - values.put(Alarms.AlarmColumns.MINUTES, 0); - - if (!values.containsKey(Alarms.AlarmColumns.DAYS_OF_WEEK)) - values.put(Alarms.AlarmColumns.DAYS_OF_WEEK, 0); - - if (!values.containsKey(Alarms.AlarmColumns.ALARM_TIME)) - values.put(Alarms.AlarmColumns.ALARM_TIME, 0); - - if (!values.containsKey(Alarms.AlarmColumns.ENABLED)) - values.put(Alarms.AlarmColumns.ENABLED, 0); - - if (!values.containsKey(Alarms.AlarmColumns.VIBRATE)) - values.put(Alarms.AlarmColumns.VIBRATE, 1); - - if (!values.containsKey(Alarms.AlarmColumns.MESSAGE)) - values.put(Alarms.AlarmColumns.MESSAGE, ""); - - if (!values.containsKey(Alarms.AlarmColumns.ALERT)) - values.put(Alarms.AlarmColumns.ALERT, ""); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - long rowId = db.insert("alarms", Alarms.AlarmColumns.MESSAGE, values); - if (rowId < 0) { - throw new SQLException("Failed to insert row into " + url); - } - if (Log.LOGV) Log.v("Added alarm rowId = " + rowId); - - Uri newUrl = ContentUris.withAppendedId(Alarms.AlarmColumns.CONTENT_URI, rowId); - getContext().getContentResolver().notifyChange(newUrl, null); - return newUrl; - } - - public int delete(Uri url, String where, String[] whereArgs) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int count; - long rowId = 0; - switch (sURLMatcher.match(url)) { - case ALARMS: - count = db.delete("alarms", where, whereArgs); - break; - case ALARMS_ID: - String segment = url.getPathSegments().get(1); - rowId = Long.parseLong(segment); - if (TextUtils.isEmpty(where)) { - where = "_id=" + segment; - } else { - where = "_id=" + segment + " AND (" + where + ")"; - } - count = db.delete("alarms", where, whereArgs); - break; - default: - throw new IllegalArgumentException("Cannot delete from URL: " + url); - } - - getContext().getContentResolver().notifyChange(url, null); - return count; - } -} diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java deleted file mode 100644 index af18b7b7a..000000000 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; - -/** - * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert - * activity. Passes through Alarm ID. - */ -public class AlarmReceiver extends BroadcastReceiver { - - /** If the alarm is older than STALE_WINDOW seconds, ignore. It - is probably the result of a time or timezone change */ - private final static int STALE_WINDOW = 60 * 30; - - @Override - public void onReceive(Context context, Intent intent) { - long now = System.currentTimeMillis(); - int id = intent.getIntExtra(Alarms.ID, 0); - long setFor = intent.getLongExtra(Alarms.TIME, 0); - - /* FIXME Intentionally verbose: always log this until we've - fully debugged the app failing to start up */ - Log.v("AlarmReceiver.onReceive() id " + id + " setFor " + setFor + - " now " + now); - - if (now > setFor + STALE_WINDOW * 1000) { - if (Log.LOGV) Log.v("AlarmReceiver ignoring stale alarm intent id" - + id + " setFor " + setFor + " now " + now); - return; - } - - /* wake device */ - AlarmAlertWakeLock.acquire(context); - - /* start audio/vibe */ - AlarmKlaxon klaxon = AlarmKlaxon.getInstance(); - klaxon.play(context, id); - - /* Close dialogs and window shade */ - Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.sendBroadcast(i); - - /* launch UI, explicitly stating that this is not due to user action - * so that the current app's notification management is not disturbed */ - Intent fireAlarm = new Intent(context, AlarmAlert.class); - fireAlarm.putExtra(Alarms.ID, id); - fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); - context.startActivity(fireAlarm); - } -} diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java deleted file mode 100644 index 56af9b310..000000000 --- a/src/com/android/alarmclock/Alarms.java +++ /dev/null @@ -1,758 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.AlarmManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.Uri; -import android.provider.BaseColumns; -import android.provider.Settings; -import android.text.format.DateFormat; - -import java.util.Calendar; - -/** - * The Alarms provider supplies info about Alarm Clock settings - */ -public class Alarms { - - public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; - public final static String ID = "alarm_id"; - public final static String TIME = "alarm_time"; - - final static String PREF_SNOOZE_ID = "snooze_id"; - final static String PREF_SNOOZE_TIME = "snooze_time"; - - private final static String DM12 = "E h:mm aa"; - private final static String DM24 = "E k:mm"; - - private final static String M12 = "h:mm aa"; - private final static String M24 = "k:mm"; - - static class DaysOfWeek { - - int mDays; - - /** - * Days of week coded as single int, convenient for DB - * storage: - * - * 0x00: no day - * 0x01: Monday - * 0x02: Tuesday - * 0x04: Wednesday - * 0x08: Thursday - * 0x10: Friday - * 0x20: Saturday - * 0x40: Sunday - */ - DaysOfWeek() { - this(0); - } - - DaysOfWeek(int days) { - mDays = days; - } - - public String toString(Context context, boolean showNever) { - StringBuilder ret = new StringBuilder(); - - /* no days */ - if (mDays == 0) return showNever ? context.getText( - R.string.never).toString() : ""; - - /* every day */ - if (mDays == 0x7f) { - return context.getText(R.string.every_day).toString(); - } - - /* count selected days */ - int dayCount = 0, days = mDays; - while (days > 0) { - if ((days & 1) == 1) dayCount++; - days >>= 1; - } - - /* short or long form? */ - CharSequence[] strings = - context.getResources().getTextArray( - (dayCount > 1) ? R.array.days_of_week_short : - R.array.days_of_week); - - /* selected days */ - for (int i = 0; i < 7; i++) { - if ((mDays & (1 << i)) != 0) { - ret.append(strings[i]); - dayCount -= 1; - if (dayCount > 0) ret.append( - context.getText(R.string.day_concat)); - } - } - return ret.toString(); - } - - /** - * @param day Mon=0 ... Sun=6 - * @return true if given day is set - */ - public boolean isSet(int day) { - return ((mDays & (1 << day)) > 0); - } - - public void set(int day, boolean set) { - if (set) { - mDays |= (1 << day); - } else { - mDays &= ~(1 << day); - } - } - - public void set(DaysOfWeek dow) { - mDays = dow.mDays; - } - - public int getCoded() { - return mDays; - } - - public boolean equals(DaysOfWeek dow) { - return mDays == dow.mDays; - } - - // Returns days of week encoded in an array of booleans. - public boolean[] getBooleanArray() { - boolean[] ret = new boolean[7]; - for (int i = 0; i < 7; i++) { - ret[i] = isSet(i); - } - return ret; - } - - public void setCoded(int days) { - mDays = days; - } - - /** - * @return true if alarm is set to repeat - */ - public boolean isRepeatSet() { - return mDays != 0; - } - - /** - * @return true if alarm is set to repeat every day - */ - public boolean isEveryDaySet() { - return mDays == 0x7f; - } - - - /** - * returns number of days from today until next alarm - * @param c must be set to today - */ - public int getNextAlarm(Calendar c) { - if (mDays == 0) return -1; - int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7; - - int day, dayCount; - for (dayCount = 0; dayCount < 7; dayCount++) { - day = (today + dayCount) % 7; - if ((mDays & (1 << day)) > 0) { - break; - } - } - return dayCount; - } - } - - public static class AlarmColumns implements BaseColumns { - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.android.alarmclock/alarm"); - - public static final String _ID = "_id"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "_id ASC"; - - /** - * Hour in 24-hour localtime 0 - 23. - *

Type: INTEGER

- */ - public static final String HOUR = "hour"; - - /** - * Minutes in localtime 0 - 59 - *

Type: INTEGER

- */ - public static final String MINUTES = "minutes"; - - /** - * Days of week coded as integer - *

Type: INTEGER

- */ - public static final String DAYS_OF_WEEK = "daysofweek"; - - /** - * Alarm time in UTC milliseconds from the epoch. - *

Type: INTEGER

- */ - public static final String ALARM_TIME = "alarmtime"; - - /** - * True if alarm is active - *

Type: BOOLEAN

- */ - public static final String ENABLED = "enabled"; - - /** - * True if alarm should vibrate - *

Type: BOOLEAN

- */ - public static final String VIBRATE = "vibrate"; - - /** - * Message to show when alarm triggers - * Note: not currently used - *

Type: STRING

- */ - public static final String MESSAGE = "message"; - - /** - * Audio alert to play when alarm triggers - *

Type: STRING

- */ - public static final String ALERT = "alert"; - - static final String[] ALARM_QUERY_COLUMNS = { - _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME, - ENABLED, VIBRATE, MESSAGE, ALERT}; - - /** - * These save calls to cursor.getColumnIndexOrThrow() - * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS - */ - public static final int ALARM_ID_INDEX = 0; - public static final int ALARM_HOUR_INDEX = 1; - public static final int ALARM_MINUTES_INDEX = 2; - public static final int ALARM_DAYS_OF_WEEK_INDEX = 3; - public static final int ALARM_TIME_INDEX = 4; - public static final int ALARM_ENABLED_INDEX = 5; - public static final int ALARM_VIBRATE_INDEX = 6; - public static final int ALARM_MESSAGE_INDEX = 7; - public static final int ALARM_ALERT_INDEX = 8; - } - - /** - * getAlarm and getAlarms call this interface to report alarms in - * the database - */ - static interface AlarmSettings { - void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert); - } - - /** - * Creates a new Alarm. - */ - public synchronized static Uri addAlarm(ContentResolver contentResolver) { - ContentValues values = new ContentValues(); - values.put(Alarms.AlarmColumns.HOUR, 8); - return contentResolver.insert(AlarmColumns.CONTENT_URI, values); - } - - /** - * Removes an existing Alarm. If this alarm is snoozing, disables - * snooze. Sets next alert. - */ - public synchronized static void deleteAlarm( - Context context, int alarmId) { - - ContentResolver contentResolver = context.getContentResolver(); - /* If alarm is snoozing, lose it */ - int snoozeId = getSnoozeAlarmId(context); - if (snoozeId == alarmId) disableSnoozeAlert(context); - - Uri uri = ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId); - deleteAlarm(contentResolver, uri); - - setNextAlert(context); - } - - private synchronized static void deleteAlarm( - ContentResolver contentResolver, Uri uri) { - contentResolver.delete(uri, "", null); - } - - /** - * Queries all alarms - * @return cursor over all alarms - */ - public synchronized static Cursor getAlarmsCursor( - ContentResolver contentResolver) { - return contentResolver.query( - AlarmColumns.CONTENT_URI, AlarmColumns.ALARM_QUERY_COLUMNS, - null, null, AlarmColumns.DEFAULT_SORT_ORDER); - } - - /** - * Calls the AlarmSettings.reportAlarm interface on all alarms found in db. - */ - public synchronized static void getAlarms( - ContentResolver contentResolver, AlarmSettings alarmSettings) { - Cursor cursor = getAlarmsCursor(contentResolver); - getAlarms(alarmSettings, cursor); - cursor.close(); - } - - private synchronized static void getAlarms( - AlarmSettings alarmSettings, Cursor cur) { - if (cur.moveToFirst()) { - do { - // Get the field values - int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX); - int hour = cur.getInt(AlarmColumns.ALARM_HOUR_INDEX); - int minutes = cur.getInt(AlarmColumns.ALARM_MINUTES_INDEX); - int daysOfWeek = cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX); - boolean enabled = cur.getInt(AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false; - boolean vibrate = cur.getInt(AlarmColumns.ALARM_VIBRATE_INDEX) == 1 ? true : false; - String message = cur.getString(AlarmColumns.ALARM_MESSAGE_INDEX); - String alert = cur.getString(AlarmColumns.ALARM_ALERT_INDEX); - alarmSettings.reportAlarm( - id, enabled, hour, minutes, new DaysOfWeek(daysOfWeek), - vibrate, message, alert); - } while (cur.moveToNext()); - } - } - - /** - * Calls the AlarmSettings.reportAlarm interface on alarm with given - * alarmId - */ - public synchronized static void getAlarm( - ContentResolver contentResolver, AlarmSettings alarmSetting, - int alarmId) { - Cursor cursor = contentResolver.query( - ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId), - AlarmColumns.ALARM_QUERY_COLUMNS, - null, null, AlarmColumns.DEFAULT_SORT_ORDER); - - getAlarms(alarmSetting, cursor); - cursor.close(); - } - - - /** - * A convenience method to set an alarm in the Alarms - * content provider. - * - * @param id corresponds to the _id column - * @param enabled corresponds to the ENABLED column - * @param hour corresponds to the HOUR column - * @param minutes corresponds to the MINUTES column - * @param daysOfWeek corresponds to the DAYS_OF_WEEK column - * @param time corresponds to the ALARM_TIME column - * @param vibrate corresponds to the VIBRATE column - * @param message corresponds to the MESSAGE column - * @param alert corresponds to the ALERT column - */ - public synchronized static void setAlarm( - Context context, int id, boolean enabled, int hour, int minutes, - DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - - ContentValues values = new ContentValues(8); - ContentResolver resolver = context.getContentResolver(); - long time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis(); - - if (Log.LOGV) Log.v( - "** setAlarm * idx " + id + " hour " + hour + " minutes " + - minutes + " enabled " + enabled + " time " + time); - - values.put(AlarmColumns.ENABLED, enabled ? 1 : 0); - values.put(AlarmColumns.HOUR, hour); - values.put(AlarmColumns.MINUTES, minutes); - values.put(AlarmColumns.ALARM_TIME, time); - values.put(AlarmColumns.DAYS_OF_WEEK, daysOfWeek.getCoded()); - values.put(AlarmColumns.VIBRATE, vibrate); - values.put(AlarmColumns.MESSAGE, message); - values.put(AlarmColumns.ALERT, alert); - resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id), - values, null, null); - - int aid = disableSnoozeAlert(context); - if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false); - setNextAlert(context); - } - - /** - * A convenience method to enable or disable an alarm. - * - * @param id corresponds to the _id column - * @param enabled corresponds to the ENABLED column - */ - - public synchronized static void enableAlarm( - final Context context, final int id, boolean enabled) { - int aid = disableSnoozeAlert(context); - if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false); - enableAlarmInternal(context, id, enabled); - setNextAlert(context); - } - - private synchronized static void enableAlarmInternal( - final Context context, final int id, boolean enabled) { - ContentResolver resolver = context.getContentResolver(); - - class EnableAlarm implements AlarmSettings { - public int mHour; - public int mMinutes; - public DaysOfWeek mDaysOfWeek; - - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - mHour = hour; - mMinutes = minutes; - mDaysOfWeek = daysOfWeek; - } - } - - ContentValues values = new ContentValues(2); - values.put(AlarmColumns.ENABLED, enabled ? 1 : 0); - - /* If we are enabling the alarm, load hour/minutes/daysOfWeek - from db, so we can calculate alarm time */ - if (enabled) { - EnableAlarm enableAlarm = new EnableAlarm(); - getAlarm(resolver, enableAlarm, id); - if (enableAlarm.mDaysOfWeek == null) { - /* Under monkey, sometimes reportAlarm is never - called */ - Log.e("** enableAlarmInternal failed " + id + " h " + - enableAlarm.mHour + " m " + enableAlarm.mMinutes); - return; - } - - long time = calculateAlarm(enableAlarm.mHour, enableAlarm.mMinutes, - enableAlarm.mDaysOfWeek).getTimeInMillis(); - values.put(AlarmColumns.ALARM_TIME, time); - } - - resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id), - values, null, null); - } - - - /** - * Calculates next scheduled alert - */ - static class AlarmCalculator implements AlarmSettings { - public long mMinAlert = Long.MAX_VALUE; - public int mMinIdx = -1; - - /** - * returns next scheduled alert, MAX_VALUE if none - */ - public long getAlert() { - return mMinAlert; - } - public int getIndex() { - return mMinIdx; - } - - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - if (enabled) { - long atTime = calculateAlarm(hour, minutes, - daysOfWeek).getTimeInMillis(); - /* Log.i("** SET ALERT* idx " + idx + " hour " + hour + " minutes " + - minutes + " enabled " + enabled + " calc " + atTime); */ - if (atTime < mMinAlert) { - mMinIdx = idx; - mMinAlert = atTime; - } - } - } - } - - static AlarmCalculator calculateNextAlert(final Context context) { - ContentResolver resolver = context.getContentResolver(); - AlarmCalculator alarmCalc = new AlarmCalculator(); - getAlarms(resolver, alarmCalc); - return alarmCalc; - } - - /** - * Disables non-repeating alarms that have passed. Called at - * boot. - */ - public static void disableExpiredAlarms(final Context context) { - Cursor cur = getAlarmsCursor(context.getContentResolver()); - long now = System.currentTimeMillis(); - - if (cur.moveToFirst()) { - do { - // Get the field values - int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX); - boolean enabled = cur.getInt( - AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false; - DaysOfWeek daysOfWeek = new DaysOfWeek( - cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); - long time = cur.getLong(AlarmColumns.ALARM_TIME_INDEX); - - if (enabled && !daysOfWeek.isRepeatSet() && time < now) { - if (Log.LOGV) Log.v( - "** DISABLE " + id + " now " + now +" set " + time); - enableAlarmInternal(context, id, false); - } - } while (cur.moveToNext()); - } - cur.close(); - } - - private static NotificationManager getNotificationManager( - final Context context) { - return (NotificationManager) context.getSystemService( - context.NOTIFICATION_SERVICE); - } - - /** - * Called at system startup, on time/timezone change, and whenever - * the user changes alarm settings. Activates snooze if set, - * otherwise loads all alarms, activates next alert. - */ - public static void setNextAlert(final Context context) { - int snoozeId = getSnoozeAlarmId(context); - if (snoozeId == -1) { - AlarmCalculator ac = calculateNextAlert(context); - int id = ac.getIndex(); - long atTime = ac.getAlert(); - - if (atTime < Long.MAX_VALUE) { - enableAlert(context, id, atTime); - } else { - disableAlert(context, id); - } - } else { - enableSnoozeAlert(context); - } - } - - /** - * Sets alert in AlarmManger and StatusBar. This is what will - * actually launch the alert when the alarm triggers. - * - * Note: In general, apps should call setNextAlert() instead of - * this method. setAlert() is only used outside this class when - * the alert is not to be driven by the state of the db. "Snooze" - * uses this API, as we do not want to alter the alarm in the db - * with each snooze. - * - * @param id Alarm ID. - * @param atTimeInMillis milliseconds since epoch - */ - static void enableAlert(Context context, int id, long atTimeInMillis) { - AlarmManager am = (AlarmManager) - context.getSystemService(Context.ALARM_SERVICE); - - Intent intent = new Intent(ALARM_ALERT_ACTION); - if (Log.LOGV) Log.v("** setAlert id " + id + " atTime " + atTimeInMillis); - intent.putExtra(ID, id); - intent.putExtra(TIME, atTimeInMillis); - PendingIntent sender = PendingIntent.getBroadcast( - context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - - if (true) { - am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); - } else { - // a five-second alarm, for testing - am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, - sender); - } - - setStatusBarIcon(context, true); - - Calendar c = Calendar.getInstance(); - c.setTime(new java.util.Date(atTimeInMillis)); - String timeString = formatDayAndTime(context, c); - saveNextAlarm(context, timeString); - } - - /** - * Disables alert in AlarmManger and StatusBar. - * - * @param id Alarm ID. - */ - static void disableAlert(Context context, int id) { - AlarmManager am = (AlarmManager) - context.getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(ALARM_ALERT_ACTION); - intent.putExtra(ID, id); - PendingIntent sender = PendingIntent.getBroadcast( - context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - am.cancel(sender); - setStatusBarIcon(context, false); - saveNextAlarm(context, ""); - } - - static void saveSnoozeAlert(final Context context, int id, - long atTimeInMillis) { - SharedPreferences prefs = context.getSharedPreferences( - AlarmClock.PREFERENCES, 0); - SharedPreferences.Editor ed = prefs.edit(); - ed.putInt(PREF_SNOOZE_ID, id); - ed.putLong(PREF_SNOOZE_TIME, atTimeInMillis); - ed.commit(); - } - - /** - * @return ID of alarm disabled, if disabled, -1 otherwise - */ - static int disableSnoozeAlert(final Context context) { - int id = getSnoozeAlarmId(context); - if (id == -1) return -1; - saveSnoozeAlert(context, -1, 0); - return id; - } - - /** - * @return alarm ID of snoozing alarm, -1 if snooze unset - */ - private static int getSnoozeAlarmId(final Context context) { - SharedPreferences prefs = context.getSharedPreferences( - AlarmClock.PREFERENCES, 0); - return prefs.getInt(PREF_SNOOZE_ID, -1); - } - - /** - * If there is a snooze set, enable it in AlarmManager - * @return true if snooze is set - */ - private static boolean enableSnoozeAlert(final Context context) { - SharedPreferences prefs = context.getSharedPreferences( - AlarmClock.PREFERENCES, 0); - - int id = prefs.getInt(PREF_SNOOZE_ID, -1); - if (id == -1) return false; - long atTimeInMillis = prefs.getLong(PREF_SNOOZE_TIME, -1); - if (id == -1) return false; - enableAlert(context, id, atTimeInMillis); - return true; - } - - - /** - * Tells the StatusBar whether the alarm is enabled or disabled - */ - private static void setStatusBarIcon(Context context, boolean enabled) { - Intent alarmChanged = new Intent(Intent.ACTION_ALARM_CHANGED); - alarmChanged.putExtra("alarmSet", enabled); - context.sendBroadcast(alarmChanged); - } - - /** - * Given an alarm in hours and minutes, return a time suitable for - * setting in AlarmManager. - * @param hour Always in 24 hour 0-23 - * @param minute 0-59 - * @param daysOfWeek 0-59 - */ - static Calendar calculateAlarm(int hour, int minute, DaysOfWeek daysOfWeek) { - - // start with now - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(System.currentTimeMillis()); - - int nowHour = c.get(Calendar.HOUR_OF_DAY); - int nowMinute = c.get(Calendar.MINUTE); - - // if alarm is behind current time, advance one day - if (hour < nowHour || - hour == nowHour && minute <= nowMinute) { - c.add(Calendar.DAY_OF_YEAR, 1); - } - c.set(Calendar.HOUR_OF_DAY, hour); - c.set(Calendar.MINUTE, minute); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - - int addDays = daysOfWeek.getNextAlarm(c); - /* Log.v("** TIMES * " + c.getTimeInMillis() + " hour " + hour + - " minute " + minute + " dow " + c.get(Calendar.DAY_OF_WEEK) + " from now " + - addDays); */ - if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays); - return c; - } - - static String formatTime(final Context context, int hour, int minute, - DaysOfWeek daysOfWeek) { - Calendar c = calculateAlarm(hour, minute, daysOfWeek); - return formatTime(context, c); - } - - /* used by AlarmAlert */ - static String formatTime(final Context context, Calendar c) { - String format = get24HourMode(context) ? M24 : M12; - return (c == null) ? "" : (String)DateFormat.format(format, c); - } - - /** - * Shows day and time -- used for lock screen - */ - private static String formatDayAndTime(final Context context, Calendar c) { - String format = get24HourMode(context) ? DM24 : DM12; - return (c == null) ? "" : (String)DateFormat.format(format, c); - } - - /** - * Save time of the next alarm, as a formatted string, into the system - * settings so those who care can make use of it. - */ - static void saveNextAlarm(final Context context, String timeString) { - Settings.System.putString(context.getContentResolver(), - Settings.System.NEXT_ALARM_FORMATTED, - timeString); - } - - /** - * @return true if clock is set to 24-hour mode - */ - static boolean get24HourMode(final Context context) { - return android.text.format.DateFormat.is24HourFormat(context); - } -} diff --git a/src/com/android/alarmclock/AnalogGadgetProvider.java b/src/com/android/alarmclock/AnalogGadgetProvider.java deleted file mode 100644 index 1143da586..000000000 --- a/src/com/android/alarmclock/AnalogGadgetProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2009 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.alarmclock; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.gadget.GadgetManager; -import android.graphics.PorterDuff; -import android.net.Uri; -import android.provider.Calendar; -import android.provider.Calendar.Attendees; -import android.provider.Calendar.Calendars; -import android.provider.Calendar.EventsColumns; -import android.provider.Calendar.Instances; -import android.provider.Calendar.Reminders; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.util.Config; -import android.util.Log; -import android.view.View; -import android.widget.RemoteViews; - -import java.util.Arrays; - -/** - * Simple gadget to show analog clock. - */ -public class AnalogGadgetProvider extends BroadcastReceiver { - static final String TAG = "AnalogGadgetProvider"; - - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) { - RemoteViews views = new RemoteViews(context.getPackageName(), - R.layout.analog_gadget); - - int[] gadgetIds = intent.getIntArrayExtra(GadgetManager.EXTRA_GADGET_IDS); - - GadgetManager gm = GadgetManager.getInstance(context); - gm.updateGadget(gadgetIds, views); - } - } -} - diff --git a/src/com/android/alarmclock/ClockPicker.java b/src/com/android/alarmclock/ClockPicker.java deleted file mode 100644 index 039f5b8a3..000000000 --- a/src/com/android/alarmclock/ClockPicker.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.alarmclock; - -import android.app.Activity; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Gallery; - -/** - * Clock face picker for the Alarm Clock application. - */ -public class ClockPicker extends Activity implements - AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener { - - private LayoutInflater mFactory; - private Gallery mGallery; - - private SharedPreferences mPrefs; - private View mClock; - private ViewGroup mClockLayout; - private int mPosition; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - requestWindowFeature(Window.FEATURE_NO_TITLE); - - mFactory = LayoutInflater.from(this); - setContentView(R.layout.clockpicker); - - mGallery = (Gallery) findViewById(R.id.gallery); - mGallery.setAdapter(new ClockAdapter()); - mGallery.setOnItemSelectedListener(this); - mGallery.setOnItemClickListener(this); - - mPrefs = getSharedPreferences(AlarmClock.PREFERENCES, 0); - int face = mPrefs.getInt(AlarmClock.PREF_CLOCK_FACE, 0); - if (face < 0 || face >= AlarmClock.CLOCKS.length) face = 0; - - mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); - mClockLayout.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - selectClock(mPosition); - } - }); - - mGallery.setSelection(face, false); - } - - public void onItemSelected(AdapterView parent, View v, int position, long id) { - if (mClock != null) { - mClockLayout.removeView(mClock); - } - mClock = mFactory.inflate(AlarmClock.CLOCKS[position], null); - mClockLayout.addView(mClock, 0); - mPosition = position; - } - - public void onItemClick(AdapterView parent, View v, int position, long id) { - selectClock(position); - } - - private synchronized void selectClock(int position) { - SharedPreferences.Editor ed = mPrefs.edit(); - ed.putInt("face", position); - ed.commit(); - - setResult(RESULT_OK); - finish(); - } - - public void onNothingSelected(AdapterView parent) { - } - - class ClockAdapter extends BaseAdapter { - - public ClockAdapter() { - } - - public int getCount() { - return AlarmClock.CLOCKS.length; - } - - public Object getItem(int position) { - return position; - } - - public long getItemId(int position) { - return position; - } - - public View getView(final int position, View convertView, ViewGroup parent) { - View clock = mFactory.inflate(AlarmClock.CLOCKS[position], null); - return clock; - } - - } -} diff --git a/src/com/android/alarmclock/DigitalClock.java b/src/com/android/alarmclock/DigitalClock.java deleted file mode 100644 index 65dcc2c9c..000000000 --- a/src/com/android/alarmclock/DigitalClock.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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.alarmclock; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.drawable.AnimationDrawable; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.provider.Settings; -import android.text.format.DateFormat; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.Calendar; - -/** - * Displays the time - */ -public class DigitalClock extends LinearLayout { - - private final static String M12 = "h:mm"; - private final static String M24 = "k:mm"; - - private Calendar mCalendar; - private String mFormat; - private TextView mTimeDisplay; - private AmPm mAmPm; - private boolean mAnimate; - private ContentObserver mFormatChangeObserver; - private boolean mLive = true; - private boolean mAttached; - - /* called by system on minute ticks */ - private final Handler mHandler = new Handler(); - private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mLive && intent.getAction().equals( - Intent.ACTION_TIMEZONE_CHANGED)) { - mCalendar = Calendar.getInstance(); - } - updateTime(); - } - }; - - static class AmPm { - private int mColorOn, mColorOff; - - private LinearLayout mAmPmLayout; - private TextView mAm, mPm; - - AmPm(View parent) { - mAmPmLayout = (LinearLayout) parent.findViewById(R.id.am_pm); - mAm = (TextView)mAmPmLayout.findViewById(R.id.am); - mPm = (TextView)mAmPmLayout.findViewById(R.id.pm); - - Resources r = parent.getResources(); - mColorOn = r.getColor(R.color.ampm_on); - mColorOff = r.getColor(R.color.ampm_off); - } - - void setShowAmPm(boolean show) { - mAmPmLayout.setVisibility(show ? View.VISIBLE : View.GONE); - } - - void setIsMorning(boolean isMorning) { - mAm.setTextColor(isMorning ? mColorOn : mColorOff); - mPm.setTextColor(isMorning ? mColorOff : mColorOn); - } - } - - private class FormatChangeObserver extends ContentObserver { - public FormatChangeObserver() { - super(new Handler()); - } - @Override - public void onChange(boolean selfChange) { - setDateFormat(); - updateTime(); - } - } - - public DigitalClock(Context context) { - this(context, null); - } - - public DigitalClock(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mTimeDisplay = (TextView) findViewById(R.id.timeDisplay); - mAmPm = new AmPm(this); - mCalendar = Calendar.getInstance(); - - setDateFormat(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (Log.LOGV) Log.v("onAttachedToWindow " + this); - - if (mAttached) return; - mAttached = true; - - if (mAnimate) { - AnimationDrawable frameAnimation = - (AnimationDrawable) mContext.getResources().getDrawable( - R.drawable.animate_circle); - View digitalClock = findViewById(R.id.digitalClock); - digitalClock.setBackgroundDrawable(frameAnimation); - /* Start the animation (looped playback by default). */ - ((AnimationDrawable) digitalClock.getBackground()).start(); - /* No luck wrapping animation or individual bitmaps in a - ScaleDrawable */ - digitalClock.requestLayout(); - } - - if (mLive) { - /* monitor time ticks, time changed, timezone */ - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_TIME_TICK); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); - } - - /* monitor 12/24-hour display preference */ - mFormatChangeObserver = new FormatChangeObserver(); - mContext.getContentResolver().registerContentObserver( - Settings.System.CONTENT_URI, true, mFormatChangeObserver); - - updateTime(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - if (!mAttached) return; - mAttached = false; - - Drawable background = getBackground(); - if (background instanceof AnimationDrawable) { - ((AnimationDrawable) background).stop(); - } - - if (mLive) { - mContext.unregisterReceiver(mIntentReceiver); - } - mContext.getContentResolver().unregisterContentObserver( - mFormatChangeObserver); - } - - - void updateTime(Calendar c) { - mCalendar = c; - updateTime(); - } - - private void updateTime() { - if (mLive) { - mCalendar.setTimeInMillis(System.currentTimeMillis()); - } - - CharSequence newTime = DateFormat.format(mFormat, mCalendar); - mTimeDisplay.setText(newTime); - mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0); - } - - private void setDateFormat() { - mFormat = Alarms.get24HourMode(mContext) ? M24 : M12; - mAmPm.setShowAmPm(mFormat == M12); - } - - void setAnimate() { - mAnimate = true; - } - - void setLive(boolean live) { - mLive = live; - } -} diff --git a/src/com/android/alarmclock/Log.java b/src/com/android/alarmclock/Log.java deleted file mode 100644 index 18cc391d0..000000000 --- a/src/com/android/alarmclock/Log.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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-level logging flag - */ - -package com.android.alarmclock; - -import android.os.SystemClock; -import android.util.Config; - -class Log { - public final static String LOGTAG = "AlarmClock"; - - static final boolean LOGV = AlarmClock.DEBUG ? Config.LOGD : Config.LOGV; - - static void v(String logMe) { - android.util.Log.v(LOGTAG, /* SystemClock.uptimeMillis() + " " + */ logMe); - } - - static void e(String logMe) { - android.util.Log.e(LOGTAG, logMe); - } - - static void e(String logMe, Exception ex) { - android.util.Log.e(LOGTAG, logMe, ex); - } -} diff --git a/src/com/android/alarmclock/RepeatPreference.java b/src/com/android/alarmclock/RepeatPreference.java deleted file mode 100644 index 8313858a4..000000000 --- a/src/com/android/alarmclock/RepeatPreference.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.alarmclock; - -import android.app.AlertDialog.Builder; -import android.content.Context; -import android.content.DialogInterface; -import android.preference.ListPreference; -import android.util.AttributeSet; - -public class RepeatPreference extends ListPreference { - - private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); - private OnRepeatChangedObserver mOnRepeatChangedObserver; - - public interface OnRepeatChangedObserver { - /** RepeatPrefrence calls this to get initial state */ - public Alarms.DaysOfWeek getDaysOfWeek(); - - /** Called when this preference has changed */ - public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek); - } - - public RepeatPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - void setOnRepeatChangedObserver(OnRepeatChangedObserver onRepeatChangedObserver) { - mOnRepeatChangedObserver = onRepeatChangedObserver; - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - if (positiveResult) { - mOnRepeatChangedObserver.onRepeatChanged(mDaysOfWeek); - } else { - /* no change -- reset to initial state */ - mDaysOfWeek.set(mOnRepeatChangedObserver.getDaysOfWeek()); - } - } - - @Override - protected void onPrepareDialogBuilder(Builder builder) { - CharSequence[] entries = getEntries(); - CharSequence[] entryValues = getEntryValues(); - - if (entries == null || entryValues == null) { - throw new IllegalStateException( - "RepeatPreference requires an entries array and an entryValues array."); - } - - mDaysOfWeek.set(mOnRepeatChangedObserver.getDaysOfWeek()); - - builder.setMultiChoiceItems( - entries, mDaysOfWeek.getBooleanArray(), - new DialogInterface.OnMultiChoiceClickListener() { - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - mDaysOfWeek.set(which, isChecked); - } - }); - } -} diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java deleted file mode 100644 index d8c9b46db..000000000 --- a/src/com/android/alarmclock/SetAlarm.java +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.Activity; -import android.app.Dialog; -import android.app.TimePickerDialog; -import android.content.Context; -import android.content.Intent; -import android.database.ContentObserver; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.preference.Preference; -import android.preference.PreferenceActivity; -import android.preference.CheckBoxPreference; -import android.preference.PreferenceScreen; -import android.text.format.DateFormat; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.TimePicker; -import android.widget.Toast; - -/** - * Manages each alarm - */ -public class SetAlarm extends PreferenceActivity - implements Alarms.AlarmSettings, TimePickerDialog.OnTimeSetListener { - - private CheckBoxPreference mAlarmOnPref; - private Preference mTimePref; - private AlarmPreference mAlarmPref; - private CheckBoxPreference mVibratePref; - private RepeatPreference mRepeatPref; - private ContentObserver mAlarmsChangeObserver; - private MenuItem mDeleteAlarmItem; - private MenuItem mTestAlarmItem; - - private int mId; - private int mHour; - private int mMinutes; - private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); - - private boolean mReportAlarmCalled; - - private static final int DIALOG_TIMEPICKER = 0; - - private class RingtoneChangedListener implements AlarmPreference.IRingtoneChangedListener { - public void onRingtoneChanged(Uri ringtoneUri) { - saveAlarm(false); - } - } - - private class OnRepeatChangedObserver implements RepeatPreference.OnRepeatChangedObserver { - public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek) { - if (!mDaysOfWeek.equals(daysOfWeek)) { - mDaysOfWeek.set(daysOfWeek); - saveAlarm(true); - } - } - public Alarms.DaysOfWeek getDaysOfWeek() { - return mDaysOfWeek; - } - } - - private class AlarmsChangeObserver extends ContentObserver { - public AlarmsChangeObserver() { - super(new Handler()); - } - @Override - public void onChange(boolean selfChange) { - Alarms.getAlarm(getContentResolver(), SetAlarm.this, mId); - } - } - - /** - * Set an alarm. Requires an Alarms.ID to be passed in as an - * extra - */ - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - addPreferencesFromResource(R.xml.alarm_prefs); - mAlarmOnPref = (CheckBoxPreference)findPreference("on"); - mTimePref = findPreference("time"); - mAlarmPref = (AlarmPreference) findPreference("alarm"); - mVibratePref = (CheckBoxPreference) findPreference("vibrate"); - mRepeatPref = (RepeatPreference) findPreference("setRepeat"); - - Intent i = getIntent(); - mId = i.getIntExtra(Alarms.ID, -1); - if (Log.LOGV) Log.v("In SetAlarm, alarm id = " + mId); - - mReportAlarmCalled = false; - /* load alarm details from database */ - Alarms.getAlarm(getContentResolver(), this, mId); - /* This should never happen, but does occasionally with the monkey. - * I believe it's a race condition where a deleted alarm is opened - * before the alarm list is refreshed. */ - if (!mReportAlarmCalled) { - Log.e("reportAlarm never called!"); - finish(); - } - - mAlarmsChangeObserver = new AlarmsChangeObserver(); - getContentResolver().registerContentObserver( - Alarms.AlarmColumns.CONTENT_URI, true, mAlarmsChangeObserver); - - mAlarmPref.setRingtoneChangedListener(new RingtoneChangedListener()); - mRepeatPref.setOnRepeatChangedObserver(new OnRepeatChangedObserver()); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - getContentResolver().unregisterContentObserver(mAlarmsChangeObserver); - } - - @Override - protected Dialog onCreateDialog(int id) { - Dialog d; - - switch (id) { - case DIALOG_TIMEPICKER: - d = new TimePickerDialog( - SetAlarm.this, - this, - 0, - 0, - DateFormat.is24HourFormat(SetAlarm.this)); - d.setTitle(getResources().getString(R.string.time)); - break; - default: - d = null; - } - - return d; - } - - @Override - protected void onPrepareDialog(int id, Dialog dialog) { - super.onPrepareDialog(id, dialog); - - switch (id) { - case DIALOG_TIMEPICKER: - TimePickerDialog timePicker = (TimePickerDialog)dialog; - timePicker.updateTime(mHour, mMinutes); - break; - } - } - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - - if (preference == mTimePref) { - showDialog(DIALOG_TIMEPICKER); - } else if (preference == mAlarmOnPref) { - saveAlarm(true); - } else if (preference == mVibratePref) { - saveAlarm(false); - } - - return super.onPreferenceTreeClick(preferenceScreen, preference); - } - - public void onTimeSet(TimePicker view, int hourOfDay, int minute) { - mHour = hourOfDay; - mMinutes = minute; - mAlarmOnPref.setChecked(true); - saveAlarm(true); - } - - /** - * Alarms.AlarmSettings implementation. Database feeds current - * settings in through this call - */ - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate,String message, - String alert) { - - mHour = hour; - mMinutes = minutes; - mAlarmOnPref.setChecked(enabled); - mDaysOfWeek.set(daysOfWeek); - mVibratePref.setChecked(vibrate); - - if (alert == null || alert.length() == 0) { - if (Log.LOGV) Log.v("****** reportAlarm null or 0-length alert"); - mAlarmPref.mAlert = getDefaultAlarm(); - if (mAlarmPref.mAlert == null) { - Log.e("****** Default Alarm null"); - } - } else { - mAlarmPref.mAlert = Uri.parse(alert); - if (mAlarmPref.mAlert == null) { - Log.e("****** Parsed null alarm. URI: " + alert); - } - } - if (Log.LOGV) Log.v("****** reportAlarm uri " + alert + " alert " + - mAlarmPref.mAlert); - updateTime(); - updateRepeat(); - updateAlarm(mAlarmPref.mAlert); - - mReportAlarmCalled = true; - } - - /** - * picks the first alarm available - */ - private Uri getDefaultAlarm() { - RingtoneManager ringtoneManager = new RingtoneManager(this); - ringtoneManager.setType(RingtoneManager.TYPE_ALARM); - return ringtoneManager.getRingtoneUri(0); - } - - private void updateTime() { - if (Log.LOGV) Log.v("updateTime " + mId); - mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, mDaysOfWeek)); - } - - private void updateAlarm(Uri ringtoneUri) { - if (Log.LOGV) Log.v("updateAlarm " + mId); - Ringtone ringtone = RingtoneManager.getRingtone(SetAlarm.this, ringtoneUri); - if (ringtone != null) { - mAlarmPref.setSummary(ringtone.getTitle(SetAlarm.this)); - } - } - - private void updateRepeat() { - if (Log.LOGV) Log.v("updateRepeat " + mId); - mRepeatPref.setSummary(mDaysOfWeek.toString(this, true)); - } - - private void saveAlarm(boolean popToast) { - if (mReportAlarmCalled && mAlarmPref.mAlert != null) { - String alertString = mAlarmPref.mAlert.toString(); - saveAlarm(this, mId, mAlarmOnPref.isChecked(), mHour, mMinutes, - mDaysOfWeek, mVibratePref.isChecked(), alertString, - popToast); - } - } - - /** - * Write alarm out to persistent store and pops toast if alarm - * enabled - */ - private static void saveAlarm( - Context context, int id, boolean enabled, int hour, int minute, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String alert, - boolean popToast) { - if (Log.LOGV) Log.v("** saveAlarm " + id + " " + enabled + " " + hour + - " " + minute + " vibe " + vibrate); - - // Fix alert string first - Alarms.setAlarm(context, id, enabled, hour, minute, daysOfWeek, vibrate, - "", alert); - - if (enabled && popToast) { - popAlarmSetToast(context, hour, minute, daysOfWeek); - } - } - - /** - * Display a toast that tells the user how long until the alarm - * goes off. This helps prevent "am/pm" mistakes. - */ - static void popAlarmSetToast(Context context, int hour, int minute, - Alarms.DaysOfWeek daysOfWeek) { - - String toastText = formatToast(context, hour, minute, daysOfWeek); - Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); - ToastMaster.setToast(toast); - toast.show(); - } - - /** - * format "Alarm set for 2 days 7 hours and 53 minutes from - * now" - */ - static String formatToast(Context context, int hour, int minute, - Alarms.DaysOfWeek daysOfWeek) { - long alarm = Alarms.calculateAlarm(hour, minute, - daysOfWeek).getTimeInMillis(); - long delta = alarm - System.currentTimeMillis();; - long hours = delta / (1000 * 60 * 60); - long minutes = delta / (1000 * 60) % 60; - long days = hours / 24; - hours = hours % 24; - - String daySeq = (days == 0) ? "" : - (days == 1) ? context.getString(R.string.day) : - context.getString(R.string.days, Long.toString(days)); - - String minSeq = (minutes == 0) ? "" : - (minutes == 1) ? context.getString(R.string.minute) : - context.getString(R.string.minutes, Long.toString(minutes)); - - String hourSeq = (hours == 0) ? "" : - (hours == 1) ? context.getString(R.string.hour) : - context.getString(R.string.hours, Long.toString(hours)); - - boolean dispDays = days > 0; - boolean dispHour = hours > 0; - boolean dispMinute = minutes > 0; - - String ret; - if (!(dispDays || dispHour || dispMinute)) { - ret = context.getString(R.string.subminute); - } else { - String parts[] = new String[5]; - parts[0] = daySeq; - parts[1] = !dispDays ? "" : - dispHour && dispMinute ? context.getString(R.string.space) : - !dispHour && !dispMinute ? "" : - context.getString(R.string.and); - parts[2] = dispHour ? hourSeq : ""; - parts[3] = dispHour && dispMinute ? context.getString(R.string.and) : ""; - parts[4] = dispMinute ? minSeq : ""; - ret = context.getString(R.string.combiner, (Object[])parts); - } - - ret = context.getString(R.string.alarm_set, ret); - /* if (Log.LOGV) Log.v("** TOAST daySeq " + daySeq + " hourSeq " + hourSeq + - " minSeq " + minSeq + " ret " + ret); */ - return ret; - } - - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - - mDeleteAlarmItem = menu.add(0, 0, 0, R.string.delete_alarm); - mDeleteAlarmItem.setIcon(android.R.drawable.ic_menu_delete); - - if (AlarmClock.DEBUG) { - mTestAlarmItem = menu.add(0, 0, 0, "test alarm"); - } - - - return true; - } - - public boolean onOptionsItemSelected(MenuItem item) { - if (item == mDeleteAlarmItem) { - Alarms.deleteAlarm(this, mId); - finish(); - return true; - } - if (AlarmClock.DEBUG) { - if (item == mTestAlarmItem) { - setTestAlarm(); - return true; - } - } - - return false; - } - - - /** - * Test code: this is disabled for production build. Sets - * this alarm to go off on the next minute - */ - void setTestAlarm() { - - // start with now - java.util.Calendar c = java.util.Calendar.getInstance(); - c.setTimeInMillis(System.currentTimeMillis()); - - int nowHour = c.get(java.util.Calendar.HOUR_OF_DAY); - int nowMinute = c.get(java.util.Calendar.MINUTE); - - int minutes = (nowMinute + 1) % 60; - int hour = nowHour + (nowMinute == 0? 1 : 0); - - saveAlarm(this, mId, true, hour, minutes, mDaysOfWeek, true, - mAlarmPref.mAlert.toString(), true); - } - -} diff --git a/src/com/android/alarmclock/ToastMaster.java b/src/com/android/alarmclock/ToastMaster.java deleted file mode 100644 index c4c281521..000000000 --- a/src/com/android/alarmclock/ToastMaster.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.alarmclock; - -import android.widget.Toast; - -public class ToastMaster { - - private static Toast sToast = null; - - private ToastMaster() { - - } - - public static void setToast(Toast toast) { - if (sToast != null) - sToast.cancel(); - sToast = toast; - } - - public static void cancelToast() { - if (sToast != null) - sToast.cancel(); - sToast = null; - } - -} -- cgit v1.2.3 From 5fedae0fb8cc29db88719329d52bdd62aad14277 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 3 Mar 2009 19:32:15 -0800 Subject: auto import from //depot/cupcake/@135843 --- src/com/android/alarmclock/AlarmAlert.java | 311 +++++++++ src/com/android/alarmclock/AlarmAlertWakeLock.java | 51 ++ src/com/android/alarmclock/AlarmClock.java | 314 +++++++++ src/com/android/alarmclock/AlarmInitReceiver.java | 44 ++ src/com/android/alarmclock/AlarmKlaxon.java | 212 ++++++ src/com/android/alarmclock/AlarmPreference.java | 54 ++ src/com/android/alarmclock/AlarmProvider.java | 237 +++++++ src/com/android/alarmclock/AlarmReceiver.java | 68 ++ src/com/android/alarmclock/Alarms.java | 758 +++++++++++++++++++++ .../android/alarmclock/AnalogGadgetProvider.java | 66 ++ src/com/android/alarmclock/ClockPicker.java | 119 ++++ src/com/android/alarmclock/DigitalClock.java | 209 ++++++ src/com/android/alarmclock/Log.java | 42 ++ src/com/android/alarmclock/RepeatPreference.java | 76 +++ src/com/android/alarmclock/SetAlarm.java | 398 +++++++++++ src/com/android/alarmclock/ToastMaster.java | 41 ++ 16 files changed, 3000 insertions(+) create mode 100644 src/com/android/alarmclock/AlarmAlert.java create mode 100644 src/com/android/alarmclock/AlarmAlertWakeLock.java create mode 100644 src/com/android/alarmclock/AlarmClock.java create mode 100644 src/com/android/alarmclock/AlarmInitReceiver.java create mode 100644 src/com/android/alarmclock/AlarmKlaxon.java create mode 100644 src/com/android/alarmclock/AlarmPreference.java create mode 100644 src/com/android/alarmclock/AlarmProvider.java create mode 100644 src/com/android/alarmclock/AlarmReceiver.java create mode 100644 src/com/android/alarmclock/Alarms.java create mode 100644 src/com/android/alarmclock/AnalogGadgetProvider.java create mode 100644 src/com/android/alarmclock/ClockPicker.java create mode 100644 src/com/android/alarmclock/DigitalClock.java create mode 100644 src/com/android/alarmclock/Log.java create mode 100644 src/com/android/alarmclock/RepeatPreference.java create mode 100644 src/com/android/alarmclock/SetAlarm.java create mode 100644 src/com/android/alarmclock/ToastMaster.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java new file mode 100644 index 000000000..eefd5bb91 --- /dev/null +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.Toast; +import android.widget.TextView; + +import java.util.Calendar; + +/** + * Alarm Clock alarm alert: pops visible indicator and plays alarm + * tone + */ +public class AlarmAlert extends Activity { + + private static final int SNOOZE_MINUTES = 10; + private static final int UNKNOWN = 0; + private static final int SNOOZE = 1; + private static final int DISMISS = 2; + private static final int KILLED = 3; + + private KeyguardManager mKeyguardManager; + private KeyguardManager.KeyguardLock mKeyguardLock = null; + private Button mSnoozeButton; + private int mState = UNKNOWN; + + private AlarmKlaxon mKlaxon; + private int mAlarmId; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + /* FIXME Intentionally verbose: always log this until we've + fully debugged the app failing to start up */ + Log.v("AlarmAlert.onCreate()"); + + mKlaxon = AlarmKlaxon.getInstance(); + + // Popup alert over black screen + WindowManager.LayoutParams lp = getWindow().getAttributes(); + lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; + lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + // XXX DO NOT COPY THIS!!! THIS IS BOGUS! Making an activity have + // a system alert type is completely broken, because the activity + // manager will still hide/show it as if it is part of the normal + // activity stack. If this is really what you want and you want it + // to work correctly, you should create and show your own custom window. + lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; + lp.token = null; + getWindow().setAttributes(lp); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + + mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + + mAlarmId = getIntent().getIntExtra(Alarms.ID, -1); + + /* allow next alarm to trigger while this activity is + active */ + Alarms.disableSnoozeAlert(AlarmAlert.this); + Alarms.disableAlert(AlarmAlert.this, mAlarmId); + Alarms.setNextAlert(this); + + mKlaxon.setKillerCallback(new AlarmKlaxon.KillerCallback() { + public void onKilled() { + if (Log.LOGV) Log.v("onKilled()"); + updateSilencedText(); + + /* don't allow snooze */ + mSnoozeButton.setEnabled(false); + + // Dismiss the alarm but mark the state as killed so if the + // config changes, we show the silenced message and disable + // snooze. + dismiss(); + mState = KILLED; + } + }); + + updateLayout(); + } + + private void updateSilencedText() { + TextView silenced = (TextView) findViewById(R.id.silencedText); + silenced.setText(getString(R.string.alarm_alert_alert_silenced, + AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60)); + silenced.setVisibility(View.VISIBLE); + } + + private void updateLayout() { + setContentView(R.layout.alarm_alert); + + /* set clock face */ + LayoutInflater mFactory = LayoutInflater.from(this); + SharedPreferences settings = + getSharedPreferences(AlarmClock.PREFERENCES, 0); + int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); + if (face < 0 || face >= AlarmClock.CLOCKS.length) { + face = 0; + } + View clockLayout = + (View) mFactory.inflate(AlarmClock.CLOCKS[face], null); + ViewGroup clockView = (ViewGroup) findViewById(R.id.clockView); + clockView.addView(clockLayout); + if (clockLayout instanceof DigitalClock) { + ((DigitalClock) clockLayout).setAnimate(); + } + + /* snooze behavior: pop a snooze confirmation view, kick alarm + manager. */ + mSnoozeButton = (Button) findViewById(R.id.snooze); + mSnoozeButton.requestFocus(); + // If this was a configuration change, keep the silenced text if the + // alarm was killed. + if (mState == KILLED) { + updateSilencedText(); + mSnoozeButton.setEnabled(false); + } else { + mSnoozeButton.setOnClickListener(new Button.OnClickListener() { + public void onClick(View v) { + snooze(); + finish(); + } + }); + } + + /* dismiss button: close notification */ + findViewById(R.id.dismiss).setOnClickListener( + new Button.OnClickListener() { + public void onClick(View v) { + dismiss(); + finish(); + } + }); + } + + // Attempt to snooze this alert. + private void snooze() { + if (mState != UNKNOWN) { + return; + } + // If the next alarm is set for sooner than the snooze interval, don't + // snooze. Instead, toast the user that the snooze will not be set. + final long snoozeTime = System.currentTimeMillis() + + (1000 * 60 * SNOOZE_MINUTES); + final long nextAlarm = + Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); + String displayTime = null; + if (nextAlarm < snoozeTime) { + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(nextAlarm); + displayTime = getString(R.string.alarm_alert_snooze_not_set, + Alarms.formatTime(AlarmAlert.this, c)); + mState = DISMISS; + } else { + Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTime); + Alarms.setNextAlert(AlarmAlert.this); + displayTime = getString(R.string.alarm_alert_snooze_set, + SNOOZE_MINUTES); + mState = SNOOZE; + } + // Intentionally log the snooze time for debugging. + Log.v(displayTime); + // Display the snooze minutes in a toast. + Toast.makeText(AlarmAlert.this, displayTime, Toast.LENGTH_LONG).show(); + mKlaxon.stop(this, mState == SNOOZE); + releaseLocks(); + } + + // Dismiss the alarm. + private void dismiss() { + if (mState != UNKNOWN) { + return; + } + mState = DISMISS; + mKlaxon.stop(this, false); + releaseLocks(); + } + + /** + * this is called when a second alarm is triggered while a + * previous alert window is still active. + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); + mState = UNKNOWN; + mSnoozeButton.setEnabled(true); + disableKeyguard(); + + mAlarmId = intent.getIntExtra(Alarms.ID, -1); + + /* unset silenced message */ + TextView silenced = (TextView)findViewById(R.id.silencedText); + silenced.setVisibility(View.GONE); + + Alarms.setNextAlert(this); + setIntent(intent); + } + + @Override + protected void onResume() { + super.onResume(); + if (Log.LOGV) Log.v("AlarmAlert.onResume()"); + disableKeyguard(); + } + + @Override + protected void onStop() { + super.onStop(); + if (Log.LOGV) Log.v("AlarmAlert.onStop()"); + // As a last resort, try to snooze if this activity is stopped. + snooze(); + } + + @Override + public void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + updateLayout(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Do this on key down to handle a few of the system keys. Only handle + // the snooze and dismiss this alert if the state is unknown. + boolean up = event.getAction() == KeyEvent.ACTION_UP; + boolean dismiss = false; + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + // Ignore ENDCALL because we do not receive the event if the screen + // is on. However, we do receive the key up for ENDCALL if the + // screen was off. + case KeyEvent.KEYCODE_ENDCALL: + break; + // Volume keys dismiss the alarm + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + dismiss = true; + // All other keys will snooze the alarm + default: + // Check for UNKNOWN here so that we intercept both key events + // and prevent the volume keys from triggering their default + // behavior. + if (mState == UNKNOWN && up) { + if (dismiss) { + dismiss(); + } else { + snooze(); + } + finish(); + } + return true; + } + return super.dispatchKeyEvent(event); + } + + private synchronized void enableKeyguard() { + if (mKeyguardLock != null) { + mKeyguardLock.reenableKeyguard(); + mKeyguardLock = null; + } + } + + private synchronized void disableKeyguard() { + if (mKeyguardLock == null) { + mKeyguardLock = mKeyguardManager.newKeyguardLock(Log.LOGTAG); + mKeyguardLock.disableKeyguard(); + } + } + + /** + * release wake and keyguard locks + */ + private synchronized void releaseLocks() { + AlarmAlertWakeLock.release(); + enableKeyguard(); + } +} diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java new file mode 100644 index 000000000..083996e58 --- /dev/null +++ b/src/com/android/alarmclock/AlarmAlertWakeLock.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.alarmclock; + +import android.content.Context; +import android.os.PowerManager; + +/** + * Hold a wakelock that can be acquired in the AlarmReceiver and + * released in the AlarmAlert activity + */ +class AlarmAlertWakeLock { + + private static PowerManager.WakeLock sWakeLock; + + static void acquire(Context context) { + if (sWakeLock != null) { + sWakeLock.release(); + } + + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + sWakeLock = pm.newWakeLock( + PowerManager.FULL_WAKE_LOCK | + PowerManager.ACQUIRE_CAUSES_WAKEUP | + PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); + sWakeLock.acquire(); + } + + static void release() { + if (Log.LOGV) Log.v("AlarmAlertWakeLock release"); + if (sWakeLock != null) { + sWakeLock.release(); + sWakeLock = null; + } + } +} diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java new file mode 100644 index 000000000..6230d16a4 --- /dev/null +++ b/src/com/android/alarmclock/AlarmClock.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.CheckBox; + +import java.util.Calendar; + +/** + * AlarmClock application. + */ +public class AlarmClock extends Activity { + + final static String PREFERENCES = "AlarmClock"; + final static int SET_ALARM = 1; + final static String PREF_CLOCK_FACE = "face"; + final static String PREF_SHOW_CLOCK = "show_clock"; + + /** Cap alarm count at this number */ + final static int MAX_ALARM_COUNT = 12; + + /** This must be false for production. If true, turns on logging, + test code, etc. */ + final static boolean DEBUG = false; + + private SharedPreferences mPrefs; + private LayoutInflater mFactory; + private ViewGroup mClockLayout; + private View mClock = null; + private MenuItem mAddAlarmItem; + private MenuItem mToggleClockItem; + private ListView mAlarmsList; + private Cursor mCursor; + + /** + * Which clock face to show + */ + private int mFace = -1; + + /* + * FIXME: it would be nice for this to live in an xml config file. + */ + final static int[] CLOCKS = { + R.layout.clock_basic_bw, + R.layout.clock_googly, + R.layout.clock_droid2, + R.layout.clock_droids, + R.layout.digital_clock + }; + + private class AlarmTimeAdapter extends CursorAdapter { + public AlarmTimeAdapter(Context context, Cursor cursor) { + super(context, cursor); + } + + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View ret = mFactory.inflate(R.layout.alarm_time, parent, false); + DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); + digitalClock.setLive(false); + if (Log.LOGV) Log.v("newView " + cursor.getPosition()); + return ret; + } + + public void bindView(View view, Context context, Cursor cursor) { + final int id = cursor.getInt(Alarms.AlarmColumns.ALARM_ID_INDEX); + final int hour = cursor.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX); + final int minutes = cursor.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX); + final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek( + cursor.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); + final boolean enabled = cursor.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1; + + CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton); + onButton.setChecked(enabled); + onButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + boolean isChecked = ((CheckBox) v).isChecked(); + Alarms.enableAlarm(AlarmClock.this, id, isChecked); + if (isChecked) { + SetAlarm.popAlarmSetToast( + AlarmClock.this, hour, minutes, daysOfWeek); + } + } + }); + + DigitalClock digitalClock = (DigitalClock)view.findViewById(R.id.digitalClock); + if (Log.LOGV) Log.v("bindView " + cursor.getPosition() + " " + id + " " + hour + + ":" + minutes + " " + daysOfWeek.toString(context, true) + " dc " + digitalClock); + + digitalClock.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (true) { + Intent intent = new Intent(AlarmClock.this, SetAlarm.class); + intent.putExtra(Alarms.ID, id); + startActivityForResult(intent, SET_ALARM); + } else { + // TESTING: immediately pop alarm + Intent fireAlarm = new Intent(AlarmClock.this, AlarmAlert.class); + fireAlarm.putExtra(Alarms.ID, id); + fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(fireAlarm); + } + } + }); + + // set the alarm text + final Calendar c = Calendar.getInstance(); + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minutes); + digitalClock.updateTime(c); + TextView daysOfWeekView = (TextView) digitalClock.findViewById(R.id.daysOfWeek); + daysOfWeekView.setText(daysOfWeek.toString(AlarmClock.this, false)); + + // Build context menu + digitalClock.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + public void onCreateContextMenu(ContextMenu menu, View view, + ContextMenuInfo menuInfo) { + menu.setHeaderTitle(Alarms.formatTime(AlarmClock.this, c)); + MenuItem deleteAlarmItem = menu.add(0, id, 0, R.string.delete_alarm); + } + }); + } + }; + + @Override + public boolean onContextItemSelected(final MenuItem item) { + // Confirm that the alarm will be deleted. + new AlertDialog.Builder(this) + .setTitle(getString(R.string.delete_alarm)) + .setMessage(getString(R.string.delete_alarm_confirm)) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface d, int w) { + Alarms.deleteAlarm(AlarmClock.this, + item.getItemId()); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + return true; + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + // sanity check -- no database, no clock + if (getContentResolver() == null) { + new AlertDialog.Builder(this) + .setTitle(getString(R.string.error)) + .setMessage(getString(R.string.dberror)) + .setPositiveButton( + android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + finish(); + }}) + .setIcon(android.R.drawable.ic_dialog_alert) + .create().show(); + return; + } + + setContentView(R.layout.alarm_clock); + mFactory = LayoutInflater.from(this); + mPrefs = getSharedPreferences(PREFERENCES, 0); + + mCursor = Alarms.getAlarmsCursor(getContentResolver()); + mAlarmsList = (ListView) findViewById(R.id.alarms_list); + mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); + mAlarmsList.setVerticalScrollBarEnabled(true); + mAlarmsList.setItemsCanFocus(true); + + mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); + mClockLayout.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + final Intent intent = new Intent(AlarmClock.this, ClockPicker.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + + setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); + } + + @Override + protected void onResume() { + super.onResume(); + + int face = mPrefs.getInt(PREF_CLOCK_FACE, 0); + if (mFace != face) { + if (face < 0 || face >= AlarmClock.CLOCKS.length) + mFace = 0; + else + mFace = face; + inflateClock(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + ToastMaster.cancelToast(); + mCursor.deactivate(); + } + + protected void inflateClock() { + if (mClock != null) { + mClockLayout.removeView(mClock); + } + mClock = mFactory.inflate(CLOCKS[mFace], null); + mClockLayout.addView(mClock, 0); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + mAddAlarmItem = menu.add(0, 0, 0, R.string.add_alarm); + mAddAlarmItem.setIcon(android.R.drawable.ic_menu_add); + + mToggleClockItem = menu.add(0, 0, 0, R.string.hide_clock); + mToggleClockItem.setIcon(R.drawable.ic_menu_clock_face); + + return true; + } + + /** + * Only allow user to add a new alarm if there are fewer than + * MAX_ALARM_COUNT + */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + mAddAlarmItem.setVisible(mAlarmsList.getChildCount() < MAX_ALARM_COUNT); + mToggleClockItem.setTitle(getClockVisibility() ? R.string.hide_clock : + R.string.show_clock); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item == mAddAlarmItem) { + Uri uri = Alarms.addAlarm(getContentResolver()); + // FIXME: scroll to new item. mAlarmsList.requestChildRectangleOnScreen() ? + String segment = uri.getPathSegments().get(1); + int newId = Integer.parseInt(segment); + if (Log.LOGV) Log.v("In AlarmClock, new alarm id = " + newId); + Intent intent = new Intent(AlarmClock.this, SetAlarm.class); + intent.putExtra(Alarms.ID, newId); + startActivityForResult(intent, SET_ALARM); + return true; + } else if (item == mToggleClockItem) { + setClockVisibility(!getClockVisibility()); + saveClockVisibility(); + return true; + } + + return false; + } + + + private boolean getClockVisibility() { + return mClockLayout.getVisibility() == View.VISIBLE; + } + + private void setClockVisibility(boolean visible) { + mClockLayout.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + private void saveClockVisibility() { + mPrefs.edit().putBoolean(PREF_SHOW_CLOCK, getClockVisibility()).commit(); + } +} diff --git a/src/com/android/alarmclock/AlarmInitReceiver.java b/src/com/android/alarmclock/AlarmInitReceiver.java new file mode 100644 index 000000000..77549b07a --- /dev/null +++ b/src/com/android/alarmclock/AlarmInitReceiver.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; + +public class AlarmInitReceiver extends BroadcastReceiver { + + /** + * Sets alarm on ACTION_BOOT_COMPLETED. Resets alarm on + * TIME_SET, TIMEZONE_CHANGED + */ + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Log.LOGV) Log.v("AlarmInitReceiver" + action); + + if (context.getContentResolver() == null) { + Log.e("AlarmInitReceiver: FAILURE unable to get content resolver. Alarms inactive."); + return; + } + if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { + Alarms.disableSnoozeAlert(context); + Alarms.disableExpiredAlarms(context); + } + Alarms.setNextAlert(context); + } +} diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java new file mode 100644 index 000000000..c862292dc --- /dev/null +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -0,0 +1,212 @@ +/* + * 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.alarmclock; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnErrorListener; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Vibrator; + +/** + * Manages alarms and vibe. Singleton, so it can be initiated in + * AlarmReceiver and shut down in the AlarmAlert activity + */ +class AlarmKlaxon implements Alarms.AlarmSettings { + + interface KillerCallback { + public void onKilled(); + } + + /** Play alarm up to 10 minutes before silencing */ + final static int ALARM_TIMEOUT_SECONDS = 10 * 60; + + private static long[] sVibratePattern = new long[] { 500, 500 }; + + private static AlarmKlaxon sInstance; + + private int mAlarmId; + private String mAlert; + private Alarms.DaysOfWeek mDaysOfWeek; + private boolean mVibrate; + + private boolean mPlaying = false; + + private Vibrator mVibrator; + private MediaPlayer mMediaPlayer; + + private Handler mTimeout; + private KillerCallback mKillerCallback; + + + static synchronized AlarmKlaxon getInstance() { + if (sInstance == null) sInstance = new AlarmKlaxon(); + return sInstance; + } + + private AlarmKlaxon() { + mVibrator = new Vibrator(); + } + + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + if (Log.LOGV) Log.v("AlarmKlaxon.reportAlarm: " + idx + " " + hour + + " " + minutes + " dow " + daysOfWeek); + mAlert = alert; + mDaysOfWeek = daysOfWeek; + mVibrate = vibrate; + } + + synchronized void play(Context context, int alarmId) { + ContentResolver contentResolver = context.getContentResolver(); + + if (mPlaying) stop(context, false); + + mAlarmId = alarmId; + + /* this will call reportAlarm() callback */ + Alarms.getAlarm(contentResolver, this, mAlarmId); + + if (Log.LOGV) Log.v("AlarmKlaxon.play() " + mAlarmId + " alert " + mAlert); + + /* play audio alert */ + if (mAlert == null) { + Log.e("Unable to play alarm: no audio file available"); + } else { + /* we need a new MediaPlayer when we change media URLs */ + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnErrorListener(new OnErrorListener() { + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e("Error occurred while playing audio."); + mp.stop(); + mp.release(); + mMediaPlayer = null; + return true; + } + }); + + try { + mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); + } catch (Exception ex) { + Log.v("Using the fallback ringtone"); + /* The alert may be on the sd card which could be busy right + * now. Use the fallback ringtone. */ + AssetFileDescriptor afd = + context.getResources().openRawResourceFd( + com.android.internal.R.raw.fallbackring); + if (afd != null) { + try { + mMediaPlayer.setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), afd.getLength()); + afd.close(); + } catch (Exception ex2) { + Log.e("Failed to play fallback ringtone", ex2); + /* At this point we just don't play anything */ + } + } + } + /* Now try to play the alert. */ + try { + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + mMediaPlayer.setLooping(true); + mMediaPlayer.prepare(); + mMediaPlayer.start(); + } catch (Exception ex) { + Log.e("Error playing alarm: " + mAlert, ex); + } + } + + /* Start the vibrator after everything is ok with the media player */ + if (mVibrate) { + mVibrator.vibrate(sVibratePattern, 0); + } else { + mVibrator.cancel(); + } + + enableKiller(); + mPlaying = true; + } + + + /** + * Stops alarm audio and disables alarm if it not snoozed and not + * repeating + */ + synchronized void stop(Context context, boolean snoozed) { + if (Log.LOGV) Log.v("AlarmKlaxon.stop() " + mAlarmId); + if (mPlaying) { + mPlaying = false; + + // Stop audio playing + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + + // Stop vibrator + mVibrator.cancel(); + + /* disable alarm only if it is not set to repeat */ + if (!snoozed && ((mDaysOfWeek == null || !mDaysOfWeek.isRepeatSet()))) { + Alarms.enableAlarm(context, mAlarmId, false); + } + } + disableKiller(); + } + + /** + * This callback called when alarm killer times out unattended + * alarm + */ + void setKillerCallback(KillerCallback killerCallback) { + mKillerCallback = killerCallback; + } + + /** + * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm + * won't run all day. + * + * This just cancels the audio, but leaves the notification + * popped, so the user will know that the alarm tripped. + */ + private void enableKiller() { + mTimeout = new Handler(); + mTimeout.postDelayed(new Runnable() { + public void run() { + if (Log.LOGV) Log.v("*********** Alarm killer triggered *************"); + if (mKillerCallback != null) mKillerCallback.onKilled(); + } + }, 1000 * ALARM_TIMEOUT_SECONDS); + } + + private void disableKiller() { + if (mTimeout != null) { + mTimeout.removeCallbacksAndMessages(null); + mTimeout = null; + } + } + + +} diff --git a/src/com/android/alarmclock/AlarmPreference.java b/src/com/android/alarmclock/AlarmPreference.java new file mode 100644 index 000000000..0fd4f89dd --- /dev/null +++ b/src/com/android/alarmclock/AlarmPreference.java @@ -0,0 +1,54 @@ +/* + * 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.alarmclock; + +import android.content.Context; +import android.net.Uri; +import android.preference.RingtonePreference; +import android.util.AttributeSet; + +public class AlarmPreference extends RingtonePreference { + public Uri mAlert; + private IRingtoneChangedListener mRingtoneChangedListener; + + public interface IRingtoneChangedListener { + public void onRingtoneChanged(Uri ringtoneUri); + }; + + public AlarmPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setRingtoneChangedListener(IRingtoneChangedListener listener) { + mRingtoneChangedListener = listener; + } + + @Override + protected void onSaveRingtone(Uri ringtoneUri) { + if (ringtoneUri != null) { + mAlert = ringtoneUri; + if (mRingtoneChangedListener != null) { + mRingtoneChangedListener.onRingtoneChanged(ringtoneUri); + } + } + } + + @Override + protected Uri onRestoreRingtone() { + return mAlert; + } +} diff --git a/src/com/android/alarmclock/AlarmProvider.java b/src/com/android/alarmclock/AlarmProvider.java new file mode 100644 index 000000000..74fdd2e89 --- /dev/null +++ b/src/com/android/alarmclock/AlarmProvider.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; + +public class AlarmProvider extends ContentProvider { + private SQLiteOpenHelper mOpenHelper; + + private static final int ALARMS = 1; + private static final int ALARMS_ID = 2; + private static final UriMatcher sURLMatcher = new UriMatcher( + UriMatcher.NO_MATCH); + + static { + sURLMatcher.addURI("com.android.alarmclock", "alarm", ALARMS); + sURLMatcher.addURI("com.android.alarmclock", "alarm/#", ALARMS_ID); + } + + private static class DatabaseHelper extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "alarms.db"; + private static final int DATABASE_VERSION = 5; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE alarms (" + + "_id INTEGER PRIMARY KEY," + + "hour INTEGER, " + + "minutes INTEGER, " + + "daysofweek INTEGER, " + + "alarmtime INTEGER, " + + "enabled INTEGER, " + + "vibrate INTEGER, " + + "message TEXT, " + + "alert TEXT);"); + + // insert default alarms + String insertMe = "INSERT INTO alarms " + + "(hour, minutes, daysofweek, alarmtime, enabled, vibrate, message, alert) " + + "VALUES "; + db.execSQL(insertMe + "(7, 0, 127, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(8, 30, 31, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(9, 00, 0, 0, 0, 1, '', '');"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { + if (Log.LOGV) Log.v( + "Upgrading alarms database from version " + + oldVersion + " to " + currentVersion + + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS alarms"); + onCreate(db); + } + } + + public AlarmProvider() { + } + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + return true; + } + + @Override + public Cursor query(Uri url, String[] projectionIn, String selection, + String[] selectionArgs, String sort) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + // Generate the body of the query + int match = sURLMatcher.match(url); + switch (match) { + case ALARMS: + qb.setTables("alarms"); + break; + case ALARMS_ID: + qb.setTables("alarms"); + qb.appendWhere("_id="); + qb.appendWhere(url.getPathSegments().get(1)); + break; + default: + throw new IllegalArgumentException("Unknown URL " + url); + } + + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, + null, null, sort); + + if (ret == null) { + if (Log.LOGV) Log.v("Alarms.query: failed"); + } else { + ret.setNotificationUri(getContext().getContentResolver(), url); + } + + return ret; + } + + @Override + public String getType(Uri url) { + int match = sURLMatcher.match(url); + switch (match) { + case ALARMS: + return "vnd.android.cursor.dir/alarms"; + case ALARMS_ID: + return "vnd.android.cursor.item/alarms"; + default: + throw new IllegalArgumentException("Unknown URL"); + } + } + + @Override + public int update(Uri url, ContentValues values, String where, String[] whereArgs) { + int count; + long rowId = 0; + int match = sURLMatcher.match(url); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + switch (match) { + case ALARMS_ID: { + String segment = url.getPathSegments().get(1); + rowId = Long.parseLong(segment); + count = db.update("alarms", values, "_id=" + rowId, null); + break; + } + default: { + throw new UnsupportedOperationException( + "Cannot update URL: " + url); + } + } + if (Log.LOGV) Log.v("*** notifyChange() rowId: " + rowId + " url " + url); + getContext().getContentResolver().notifyChange(url, null); + return count; + } + + @Override + public Uri insert(Uri url, ContentValues initialValues) { + if (sURLMatcher.match(url) != ALARMS) { + throw new IllegalArgumentException("Cannot insert into URL: " + url); + } + + ContentValues values; + if (initialValues != null) + values = new ContentValues(initialValues); + else + values = new ContentValues(); + + if (!values.containsKey(Alarms.AlarmColumns.HOUR)) + values.put(Alarms.AlarmColumns.HOUR, 0); + + if (!values.containsKey(Alarms.AlarmColumns.MINUTES)) + values.put(Alarms.AlarmColumns.MINUTES, 0); + + if (!values.containsKey(Alarms.AlarmColumns.DAYS_OF_WEEK)) + values.put(Alarms.AlarmColumns.DAYS_OF_WEEK, 0); + + if (!values.containsKey(Alarms.AlarmColumns.ALARM_TIME)) + values.put(Alarms.AlarmColumns.ALARM_TIME, 0); + + if (!values.containsKey(Alarms.AlarmColumns.ENABLED)) + values.put(Alarms.AlarmColumns.ENABLED, 0); + + if (!values.containsKey(Alarms.AlarmColumns.VIBRATE)) + values.put(Alarms.AlarmColumns.VIBRATE, 1); + + if (!values.containsKey(Alarms.AlarmColumns.MESSAGE)) + values.put(Alarms.AlarmColumns.MESSAGE, ""); + + if (!values.containsKey(Alarms.AlarmColumns.ALERT)) + values.put(Alarms.AlarmColumns.ALERT, ""); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert("alarms", Alarms.AlarmColumns.MESSAGE, values); + if (rowId < 0) { + throw new SQLException("Failed to insert row into " + url); + } + if (Log.LOGV) Log.v("Added alarm rowId = " + rowId); + + Uri newUrl = ContentUris.withAppendedId(Alarms.AlarmColumns.CONTENT_URI, rowId); + getContext().getContentResolver().notifyChange(newUrl, null); + return newUrl; + } + + public int delete(Uri url, String where, String[] whereArgs) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count; + long rowId = 0; + switch (sURLMatcher.match(url)) { + case ALARMS: + count = db.delete("alarms", where, whereArgs); + break; + case ALARMS_ID: + String segment = url.getPathSegments().get(1); + rowId = Long.parseLong(segment); + if (TextUtils.isEmpty(where)) { + where = "_id=" + segment; + } else { + where = "_id=" + segment + " AND (" + where + ")"; + } + count = db.delete("alarms", where, whereArgs); + break; + default: + throw new IllegalArgumentException("Cannot delete from URL: " + url); + } + + getContext().getContentResolver().notifyChange(url, null); + return count; + } +} diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java new file mode 100644 index 000000000..af18b7b7a --- /dev/null +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; + +/** + * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert + * activity. Passes through Alarm ID. + */ +public class AlarmReceiver extends BroadcastReceiver { + + /** If the alarm is older than STALE_WINDOW seconds, ignore. It + is probably the result of a time or timezone change */ + private final static int STALE_WINDOW = 60 * 30; + + @Override + public void onReceive(Context context, Intent intent) { + long now = System.currentTimeMillis(); + int id = intent.getIntExtra(Alarms.ID, 0); + long setFor = intent.getLongExtra(Alarms.TIME, 0); + + /* FIXME Intentionally verbose: always log this until we've + fully debugged the app failing to start up */ + Log.v("AlarmReceiver.onReceive() id " + id + " setFor " + setFor + + " now " + now); + + if (now > setFor + STALE_WINDOW * 1000) { + if (Log.LOGV) Log.v("AlarmReceiver ignoring stale alarm intent id" + + id + " setFor " + setFor + " now " + now); + return; + } + + /* wake device */ + AlarmAlertWakeLock.acquire(context); + + /* start audio/vibe */ + AlarmKlaxon klaxon = AlarmKlaxon.getInstance(); + klaxon.play(context, id); + + /* Close dialogs and window shade */ + Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.sendBroadcast(i); + + /* launch UI, explicitly stating that this is not due to user action + * so that the current app's notification management is not disturbed */ + Intent fireAlarm = new Intent(context, AlarmAlert.class); + fireAlarm.putExtra(Alarms.ID, id); + fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); + context.startActivity(fireAlarm); + } +} diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java new file mode 100644 index 000000000..56af9b310 --- /dev/null +++ b/src/com/android/alarmclock/Alarms.java @@ -0,0 +1,758 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.provider.BaseColumns; +import android.provider.Settings; +import android.text.format.DateFormat; + +import java.util.Calendar; + +/** + * The Alarms provider supplies info about Alarm Clock settings + */ +public class Alarms { + + public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; + public final static String ID = "alarm_id"; + public final static String TIME = "alarm_time"; + + final static String PREF_SNOOZE_ID = "snooze_id"; + final static String PREF_SNOOZE_TIME = "snooze_time"; + + private final static String DM12 = "E h:mm aa"; + private final static String DM24 = "E k:mm"; + + private final static String M12 = "h:mm aa"; + private final static String M24 = "k:mm"; + + static class DaysOfWeek { + + int mDays; + + /** + * Days of week coded as single int, convenient for DB + * storage: + * + * 0x00: no day + * 0x01: Monday + * 0x02: Tuesday + * 0x04: Wednesday + * 0x08: Thursday + * 0x10: Friday + * 0x20: Saturday + * 0x40: Sunday + */ + DaysOfWeek() { + this(0); + } + + DaysOfWeek(int days) { + mDays = days; + } + + public String toString(Context context, boolean showNever) { + StringBuilder ret = new StringBuilder(); + + /* no days */ + if (mDays == 0) return showNever ? context.getText( + R.string.never).toString() : ""; + + /* every day */ + if (mDays == 0x7f) { + return context.getText(R.string.every_day).toString(); + } + + /* count selected days */ + int dayCount = 0, days = mDays; + while (days > 0) { + if ((days & 1) == 1) dayCount++; + days >>= 1; + } + + /* short or long form? */ + CharSequence[] strings = + context.getResources().getTextArray( + (dayCount > 1) ? R.array.days_of_week_short : + R.array.days_of_week); + + /* selected days */ + for (int i = 0; i < 7; i++) { + if ((mDays & (1 << i)) != 0) { + ret.append(strings[i]); + dayCount -= 1; + if (dayCount > 0) ret.append( + context.getText(R.string.day_concat)); + } + } + return ret.toString(); + } + + /** + * @param day Mon=0 ... Sun=6 + * @return true if given day is set + */ + public boolean isSet(int day) { + return ((mDays & (1 << day)) > 0); + } + + public void set(int day, boolean set) { + if (set) { + mDays |= (1 << day); + } else { + mDays &= ~(1 << day); + } + } + + public void set(DaysOfWeek dow) { + mDays = dow.mDays; + } + + public int getCoded() { + return mDays; + } + + public boolean equals(DaysOfWeek dow) { + return mDays == dow.mDays; + } + + // Returns days of week encoded in an array of booleans. + public boolean[] getBooleanArray() { + boolean[] ret = new boolean[7]; + for (int i = 0; i < 7; i++) { + ret[i] = isSet(i); + } + return ret; + } + + public void setCoded(int days) { + mDays = days; + } + + /** + * @return true if alarm is set to repeat + */ + public boolean isRepeatSet() { + return mDays != 0; + } + + /** + * @return true if alarm is set to repeat every day + */ + public boolean isEveryDaySet() { + return mDays == 0x7f; + } + + + /** + * returns number of days from today until next alarm + * @param c must be set to today + */ + public int getNextAlarm(Calendar c) { + if (mDays == 0) return -1; + int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7; + + int day, dayCount; + for (dayCount = 0; dayCount < 7; dayCount++) { + day = (today + dayCount) % 7; + if ((mDays & (1 << day)) > 0) { + break; + } + } + return dayCount; + } + } + + public static class AlarmColumns implements BaseColumns { + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://com.android.alarmclock/alarm"); + + public static final String _ID = "_id"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "_id ASC"; + + /** + * Hour in 24-hour localtime 0 - 23. + *

Type: INTEGER

+ */ + public static final String HOUR = "hour"; + + /** + * Minutes in localtime 0 - 59 + *

Type: INTEGER

+ */ + public static final String MINUTES = "minutes"; + + /** + * Days of week coded as integer + *

Type: INTEGER

+ */ + public static final String DAYS_OF_WEEK = "daysofweek"; + + /** + * Alarm time in UTC milliseconds from the epoch. + *

Type: INTEGER

+ */ + public static final String ALARM_TIME = "alarmtime"; + + /** + * True if alarm is active + *

Type: BOOLEAN

+ */ + public static final String ENABLED = "enabled"; + + /** + * True if alarm should vibrate + *

Type: BOOLEAN

+ */ + public static final String VIBRATE = "vibrate"; + + /** + * Message to show when alarm triggers + * Note: not currently used + *

Type: STRING

+ */ + public static final String MESSAGE = "message"; + + /** + * Audio alert to play when alarm triggers + *

Type: STRING

+ */ + public static final String ALERT = "alert"; + + static final String[] ALARM_QUERY_COLUMNS = { + _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME, + ENABLED, VIBRATE, MESSAGE, ALERT}; + + /** + * These save calls to cursor.getColumnIndexOrThrow() + * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS + */ + public static final int ALARM_ID_INDEX = 0; + public static final int ALARM_HOUR_INDEX = 1; + public static final int ALARM_MINUTES_INDEX = 2; + public static final int ALARM_DAYS_OF_WEEK_INDEX = 3; + public static final int ALARM_TIME_INDEX = 4; + public static final int ALARM_ENABLED_INDEX = 5; + public static final int ALARM_VIBRATE_INDEX = 6; + public static final int ALARM_MESSAGE_INDEX = 7; + public static final int ALARM_ALERT_INDEX = 8; + } + + /** + * getAlarm and getAlarms call this interface to report alarms in + * the database + */ + static interface AlarmSettings { + void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert); + } + + /** + * Creates a new Alarm. + */ + public synchronized static Uri addAlarm(ContentResolver contentResolver) { + ContentValues values = new ContentValues(); + values.put(Alarms.AlarmColumns.HOUR, 8); + return contentResolver.insert(AlarmColumns.CONTENT_URI, values); + } + + /** + * Removes an existing Alarm. If this alarm is snoozing, disables + * snooze. Sets next alert. + */ + public synchronized static void deleteAlarm( + Context context, int alarmId) { + + ContentResolver contentResolver = context.getContentResolver(); + /* If alarm is snoozing, lose it */ + int snoozeId = getSnoozeAlarmId(context); + if (snoozeId == alarmId) disableSnoozeAlert(context); + + Uri uri = ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId); + deleteAlarm(contentResolver, uri); + + setNextAlert(context); + } + + private synchronized static void deleteAlarm( + ContentResolver contentResolver, Uri uri) { + contentResolver.delete(uri, "", null); + } + + /** + * Queries all alarms + * @return cursor over all alarms + */ + public synchronized static Cursor getAlarmsCursor( + ContentResolver contentResolver) { + return contentResolver.query( + AlarmColumns.CONTENT_URI, AlarmColumns.ALARM_QUERY_COLUMNS, + null, null, AlarmColumns.DEFAULT_SORT_ORDER); + } + + /** + * Calls the AlarmSettings.reportAlarm interface on all alarms found in db. + */ + public synchronized static void getAlarms( + ContentResolver contentResolver, AlarmSettings alarmSettings) { + Cursor cursor = getAlarmsCursor(contentResolver); + getAlarms(alarmSettings, cursor); + cursor.close(); + } + + private synchronized static void getAlarms( + AlarmSettings alarmSettings, Cursor cur) { + if (cur.moveToFirst()) { + do { + // Get the field values + int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX); + int hour = cur.getInt(AlarmColumns.ALARM_HOUR_INDEX); + int minutes = cur.getInt(AlarmColumns.ALARM_MINUTES_INDEX); + int daysOfWeek = cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX); + boolean enabled = cur.getInt(AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false; + boolean vibrate = cur.getInt(AlarmColumns.ALARM_VIBRATE_INDEX) == 1 ? true : false; + String message = cur.getString(AlarmColumns.ALARM_MESSAGE_INDEX); + String alert = cur.getString(AlarmColumns.ALARM_ALERT_INDEX); + alarmSettings.reportAlarm( + id, enabled, hour, minutes, new DaysOfWeek(daysOfWeek), + vibrate, message, alert); + } while (cur.moveToNext()); + } + } + + /** + * Calls the AlarmSettings.reportAlarm interface on alarm with given + * alarmId + */ + public synchronized static void getAlarm( + ContentResolver contentResolver, AlarmSettings alarmSetting, + int alarmId) { + Cursor cursor = contentResolver.query( + ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId), + AlarmColumns.ALARM_QUERY_COLUMNS, + null, null, AlarmColumns.DEFAULT_SORT_ORDER); + + getAlarms(alarmSetting, cursor); + cursor.close(); + } + + + /** + * A convenience method to set an alarm in the Alarms + * content provider. + * + * @param id corresponds to the _id column + * @param enabled corresponds to the ENABLED column + * @param hour corresponds to the HOUR column + * @param minutes corresponds to the MINUTES column + * @param daysOfWeek corresponds to the DAYS_OF_WEEK column + * @param time corresponds to the ALARM_TIME column + * @param vibrate corresponds to the VIBRATE column + * @param message corresponds to the MESSAGE column + * @param alert corresponds to the ALERT column + */ + public synchronized static void setAlarm( + Context context, int id, boolean enabled, int hour, int minutes, + DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + + ContentValues values = new ContentValues(8); + ContentResolver resolver = context.getContentResolver(); + long time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis(); + + if (Log.LOGV) Log.v( + "** setAlarm * idx " + id + " hour " + hour + " minutes " + + minutes + " enabled " + enabled + " time " + time); + + values.put(AlarmColumns.ENABLED, enabled ? 1 : 0); + values.put(AlarmColumns.HOUR, hour); + values.put(AlarmColumns.MINUTES, minutes); + values.put(AlarmColumns.ALARM_TIME, time); + values.put(AlarmColumns.DAYS_OF_WEEK, daysOfWeek.getCoded()); + values.put(AlarmColumns.VIBRATE, vibrate); + values.put(AlarmColumns.MESSAGE, message); + values.put(AlarmColumns.ALERT, alert); + resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id), + values, null, null); + + int aid = disableSnoozeAlert(context); + if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false); + setNextAlert(context); + } + + /** + * A convenience method to enable or disable an alarm. + * + * @param id corresponds to the _id column + * @param enabled corresponds to the ENABLED column + */ + + public synchronized static void enableAlarm( + final Context context, final int id, boolean enabled) { + int aid = disableSnoozeAlert(context); + if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false); + enableAlarmInternal(context, id, enabled); + setNextAlert(context); + } + + private synchronized static void enableAlarmInternal( + final Context context, final int id, boolean enabled) { + ContentResolver resolver = context.getContentResolver(); + + class EnableAlarm implements AlarmSettings { + public int mHour; + public int mMinutes; + public DaysOfWeek mDaysOfWeek; + + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + mHour = hour; + mMinutes = minutes; + mDaysOfWeek = daysOfWeek; + } + } + + ContentValues values = new ContentValues(2); + values.put(AlarmColumns.ENABLED, enabled ? 1 : 0); + + /* If we are enabling the alarm, load hour/minutes/daysOfWeek + from db, so we can calculate alarm time */ + if (enabled) { + EnableAlarm enableAlarm = new EnableAlarm(); + getAlarm(resolver, enableAlarm, id); + if (enableAlarm.mDaysOfWeek == null) { + /* Under monkey, sometimes reportAlarm is never + called */ + Log.e("** enableAlarmInternal failed " + id + " h " + + enableAlarm.mHour + " m " + enableAlarm.mMinutes); + return; + } + + long time = calculateAlarm(enableAlarm.mHour, enableAlarm.mMinutes, + enableAlarm.mDaysOfWeek).getTimeInMillis(); + values.put(AlarmColumns.ALARM_TIME, time); + } + + resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id), + values, null, null); + } + + + /** + * Calculates next scheduled alert + */ + static class AlarmCalculator implements AlarmSettings { + public long mMinAlert = Long.MAX_VALUE; + public int mMinIdx = -1; + + /** + * returns next scheduled alert, MAX_VALUE if none + */ + public long getAlert() { + return mMinAlert; + } + public int getIndex() { + return mMinIdx; + } + + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + if (enabled) { + long atTime = calculateAlarm(hour, minutes, + daysOfWeek).getTimeInMillis(); + /* Log.i("** SET ALERT* idx " + idx + " hour " + hour + " minutes " + + minutes + " enabled " + enabled + " calc " + atTime); */ + if (atTime < mMinAlert) { + mMinIdx = idx; + mMinAlert = atTime; + } + } + } + } + + static AlarmCalculator calculateNextAlert(final Context context) { + ContentResolver resolver = context.getContentResolver(); + AlarmCalculator alarmCalc = new AlarmCalculator(); + getAlarms(resolver, alarmCalc); + return alarmCalc; + } + + /** + * Disables non-repeating alarms that have passed. Called at + * boot. + */ + public static void disableExpiredAlarms(final Context context) { + Cursor cur = getAlarmsCursor(context.getContentResolver()); + long now = System.currentTimeMillis(); + + if (cur.moveToFirst()) { + do { + // Get the field values + int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX); + boolean enabled = cur.getInt( + AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false; + DaysOfWeek daysOfWeek = new DaysOfWeek( + cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); + long time = cur.getLong(AlarmColumns.ALARM_TIME_INDEX); + + if (enabled && !daysOfWeek.isRepeatSet() && time < now) { + if (Log.LOGV) Log.v( + "** DISABLE " + id + " now " + now +" set " + time); + enableAlarmInternal(context, id, false); + } + } while (cur.moveToNext()); + } + cur.close(); + } + + private static NotificationManager getNotificationManager( + final Context context) { + return (NotificationManager) context.getSystemService( + context.NOTIFICATION_SERVICE); + } + + /** + * Called at system startup, on time/timezone change, and whenever + * the user changes alarm settings. Activates snooze if set, + * otherwise loads all alarms, activates next alert. + */ + public static void setNextAlert(final Context context) { + int snoozeId = getSnoozeAlarmId(context); + if (snoozeId == -1) { + AlarmCalculator ac = calculateNextAlert(context); + int id = ac.getIndex(); + long atTime = ac.getAlert(); + + if (atTime < Long.MAX_VALUE) { + enableAlert(context, id, atTime); + } else { + disableAlert(context, id); + } + } else { + enableSnoozeAlert(context); + } + } + + /** + * Sets alert in AlarmManger and StatusBar. This is what will + * actually launch the alert when the alarm triggers. + * + * Note: In general, apps should call setNextAlert() instead of + * this method. setAlert() is only used outside this class when + * the alert is not to be driven by the state of the db. "Snooze" + * uses this API, as we do not want to alter the alarm in the db + * with each snooze. + * + * @param id Alarm ID. + * @param atTimeInMillis milliseconds since epoch + */ + static void enableAlert(Context context, int id, long atTimeInMillis) { + AlarmManager am = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + + Intent intent = new Intent(ALARM_ALERT_ACTION); + if (Log.LOGV) Log.v("** setAlert id " + id + " atTime " + atTimeInMillis); + intent.putExtra(ID, id); + intent.putExtra(TIME, atTimeInMillis); + PendingIntent sender = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + + if (true) { + am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); + } else { + // a five-second alarm, for testing + am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, + sender); + } + + setStatusBarIcon(context, true); + + Calendar c = Calendar.getInstance(); + c.setTime(new java.util.Date(atTimeInMillis)); + String timeString = formatDayAndTime(context, c); + saveNextAlarm(context, timeString); + } + + /** + * Disables alert in AlarmManger and StatusBar. + * + * @param id Alarm ID. + */ + static void disableAlert(Context context, int id) { + AlarmManager am = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(ALARM_ALERT_ACTION); + intent.putExtra(ID, id); + PendingIntent sender = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + am.cancel(sender); + setStatusBarIcon(context, false); + saveNextAlarm(context, ""); + } + + static void saveSnoozeAlert(final Context context, int id, + long atTimeInMillis) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + SharedPreferences.Editor ed = prefs.edit(); + ed.putInt(PREF_SNOOZE_ID, id); + ed.putLong(PREF_SNOOZE_TIME, atTimeInMillis); + ed.commit(); + } + + /** + * @return ID of alarm disabled, if disabled, -1 otherwise + */ + static int disableSnoozeAlert(final Context context) { + int id = getSnoozeAlarmId(context); + if (id == -1) return -1; + saveSnoozeAlert(context, -1, 0); + return id; + } + + /** + * @return alarm ID of snoozing alarm, -1 if snooze unset + */ + private static int getSnoozeAlarmId(final Context context) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + return prefs.getInt(PREF_SNOOZE_ID, -1); + } + + /** + * If there is a snooze set, enable it in AlarmManager + * @return true if snooze is set + */ + private static boolean enableSnoozeAlert(final Context context) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + + int id = prefs.getInt(PREF_SNOOZE_ID, -1); + if (id == -1) return false; + long atTimeInMillis = prefs.getLong(PREF_SNOOZE_TIME, -1); + if (id == -1) return false; + enableAlert(context, id, atTimeInMillis); + return true; + } + + + /** + * Tells the StatusBar whether the alarm is enabled or disabled + */ + private static void setStatusBarIcon(Context context, boolean enabled) { + Intent alarmChanged = new Intent(Intent.ACTION_ALARM_CHANGED); + alarmChanged.putExtra("alarmSet", enabled); + context.sendBroadcast(alarmChanged); + } + + /** + * Given an alarm in hours and minutes, return a time suitable for + * setting in AlarmManager. + * @param hour Always in 24 hour 0-23 + * @param minute 0-59 + * @param daysOfWeek 0-59 + */ + static Calendar calculateAlarm(int hour, int minute, DaysOfWeek daysOfWeek) { + + // start with now + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(System.currentTimeMillis()); + + int nowHour = c.get(Calendar.HOUR_OF_DAY); + int nowMinute = c.get(Calendar.MINUTE); + + // if alarm is behind current time, advance one day + if (hour < nowHour || + hour == nowHour && minute <= nowMinute) { + c.add(Calendar.DAY_OF_YEAR, 1); + } + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + int addDays = daysOfWeek.getNextAlarm(c); + /* Log.v("** TIMES * " + c.getTimeInMillis() + " hour " + hour + + " minute " + minute + " dow " + c.get(Calendar.DAY_OF_WEEK) + " from now " + + addDays); */ + if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays); + return c; + } + + static String formatTime(final Context context, int hour, int minute, + DaysOfWeek daysOfWeek) { + Calendar c = calculateAlarm(hour, minute, daysOfWeek); + return formatTime(context, c); + } + + /* used by AlarmAlert */ + static String formatTime(final Context context, Calendar c) { + String format = get24HourMode(context) ? M24 : M12; + return (c == null) ? "" : (String)DateFormat.format(format, c); + } + + /** + * Shows day and time -- used for lock screen + */ + private static String formatDayAndTime(final Context context, Calendar c) { + String format = get24HourMode(context) ? DM24 : DM12; + return (c == null) ? "" : (String)DateFormat.format(format, c); + } + + /** + * Save time of the next alarm, as a formatted string, into the system + * settings so those who care can make use of it. + */ + static void saveNextAlarm(final Context context, String timeString) { + Settings.System.putString(context.getContentResolver(), + Settings.System.NEXT_ALARM_FORMATTED, + timeString); + } + + /** + * @return true if clock is set to 24-hour mode + */ + static boolean get24HourMode(final Context context) { + return android.text.format.DateFormat.is24HourFormat(context); + } +} diff --git a/src/com/android/alarmclock/AnalogGadgetProvider.java b/src/com/android/alarmclock/AnalogGadgetProvider.java new file mode 100644 index 000000000..1143da586 --- /dev/null +++ b/src/com/android/alarmclock/AnalogGadgetProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2009 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.alarmclock; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.database.Cursor; +import android.gadget.GadgetManager; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.provider.Calendar; +import android.provider.Calendar.Attendees; +import android.provider.Calendar.Calendars; +import android.provider.Calendar.EventsColumns; +import android.provider.Calendar.Instances; +import android.provider.Calendar.Reminders; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Config; +import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; + +import java.util.Arrays; + +/** + * Simple gadget to show analog clock. + */ +public class AnalogGadgetProvider extends BroadcastReceiver { + static final String TAG = "AnalogGadgetProvider"; + + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) { + RemoteViews views = new RemoteViews(context.getPackageName(), + R.layout.analog_gadget); + + int[] gadgetIds = intent.getIntArrayExtra(GadgetManager.EXTRA_GADGET_IDS); + + GadgetManager gm = GadgetManager.getInstance(context); + gm.updateGadget(gadgetIds, views); + } + } +} + diff --git a/src/com/android/alarmclock/ClockPicker.java b/src/com/android/alarmclock/ClockPicker.java new file mode 100644 index 000000000..039f5b8a3 --- /dev/null +++ b/src/com/android/alarmclock/ClockPicker.java @@ -0,0 +1,119 @@ +/* + * 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.alarmclock; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Gallery; + +/** + * Clock face picker for the Alarm Clock application. + */ +public class ClockPicker extends Activity implements + AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener { + + private LayoutInflater mFactory; + private Gallery mGallery; + + private SharedPreferences mPrefs; + private View mClock; + private ViewGroup mClockLayout; + private int mPosition; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + mFactory = LayoutInflater.from(this); + setContentView(R.layout.clockpicker); + + mGallery = (Gallery) findViewById(R.id.gallery); + mGallery.setAdapter(new ClockAdapter()); + mGallery.setOnItemSelectedListener(this); + mGallery.setOnItemClickListener(this); + + mPrefs = getSharedPreferences(AlarmClock.PREFERENCES, 0); + int face = mPrefs.getInt(AlarmClock.PREF_CLOCK_FACE, 0); + if (face < 0 || face >= AlarmClock.CLOCKS.length) face = 0; + + mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); + mClockLayout.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + selectClock(mPosition); + } + }); + + mGallery.setSelection(face, false); + } + + public void onItemSelected(AdapterView parent, View v, int position, long id) { + if (mClock != null) { + mClockLayout.removeView(mClock); + } + mClock = mFactory.inflate(AlarmClock.CLOCKS[position], null); + mClockLayout.addView(mClock, 0); + mPosition = position; + } + + public void onItemClick(AdapterView parent, View v, int position, long id) { + selectClock(position); + } + + private synchronized void selectClock(int position) { + SharedPreferences.Editor ed = mPrefs.edit(); + ed.putInt("face", position); + ed.commit(); + + setResult(RESULT_OK); + finish(); + } + + public void onNothingSelected(AdapterView parent) { + } + + class ClockAdapter extends BaseAdapter { + + public ClockAdapter() { + } + + public int getCount() { + return AlarmClock.CLOCKS.length; + } + + public Object getItem(int position) { + return position; + } + + public long getItemId(int position) { + return position; + } + + public View getView(final int position, View convertView, ViewGroup parent) { + View clock = mFactory.inflate(AlarmClock.CLOCKS[position], null); + return clock; + } + + } +} diff --git a/src/com/android/alarmclock/DigitalClock.java b/src/com/android/alarmclock/DigitalClock.java new file mode 100644 index 000000000..65dcc2c9c --- /dev/null +++ b/src/com/android/alarmclock/DigitalClock.java @@ -0,0 +1,209 @@ +/* + * 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.alarmclock; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.provider.Settings; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.Calendar; + +/** + * Displays the time + */ +public class DigitalClock extends LinearLayout { + + private final static String M12 = "h:mm"; + private final static String M24 = "k:mm"; + + private Calendar mCalendar; + private String mFormat; + private TextView mTimeDisplay; + private AmPm mAmPm; + private boolean mAnimate; + private ContentObserver mFormatChangeObserver; + private boolean mLive = true; + private boolean mAttached; + + /* called by system on minute ticks */ + private final Handler mHandler = new Handler(); + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mLive && intent.getAction().equals( + Intent.ACTION_TIMEZONE_CHANGED)) { + mCalendar = Calendar.getInstance(); + } + updateTime(); + } + }; + + static class AmPm { + private int mColorOn, mColorOff; + + private LinearLayout mAmPmLayout; + private TextView mAm, mPm; + + AmPm(View parent) { + mAmPmLayout = (LinearLayout) parent.findViewById(R.id.am_pm); + mAm = (TextView)mAmPmLayout.findViewById(R.id.am); + mPm = (TextView)mAmPmLayout.findViewById(R.id.pm); + + Resources r = parent.getResources(); + mColorOn = r.getColor(R.color.ampm_on); + mColorOff = r.getColor(R.color.ampm_off); + } + + void setShowAmPm(boolean show) { + mAmPmLayout.setVisibility(show ? View.VISIBLE : View.GONE); + } + + void setIsMorning(boolean isMorning) { + mAm.setTextColor(isMorning ? mColorOn : mColorOff); + mPm.setTextColor(isMorning ? mColorOff : mColorOn); + } + } + + private class FormatChangeObserver extends ContentObserver { + public FormatChangeObserver() { + super(new Handler()); + } + @Override + public void onChange(boolean selfChange) { + setDateFormat(); + updateTime(); + } + } + + public DigitalClock(Context context) { + this(context, null); + } + + public DigitalClock(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mTimeDisplay = (TextView) findViewById(R.id.timeDisplay); + mAmPm = new AmPm(this); + mCalendar = Calendar.getInstance(); + + setDateFormat(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (Log.LOGV) Log.v("onAttachedToWindow " + this); + + if (mAttached) return; + mAttached = true; + + if (mAnimate) { + AnimationDrawable frameAnimation = + (AnimationDrawable) mContext.getResources().getDrawable( + R.drawable.animate_circle); + View digitalClock = findViewById(R.id.digitalClock); + digitalClock.setBackgroundDrawable(frameAnimation); + /* Start the animation (looped playback by default). */ + ((AnimationDrawable) digitalClock.getBackground()).start(); + /* No luck wrapping animation or individual bitmaps in a + ScaleDrawable */ + digitalClock.requestLayout(); + } + + if (mLive) { + /* monitor time ticks, time changed, timezone */ + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + } + + /* monitor 12/24-hour display preference */ + mFormatChangeObserver = new FormatChangeObserver(); + mContext.getContentResolver().registerContentObserver( + Settings.System.CONTENT_URI, true, mFormatChangeObserver); + + updateTime(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (!mAttached) return; + mAttached = false; + + Drawable background = getBackground(); + if (background instanceof AnimationDrawable) { + ((AnimationDrawable) background).stop(); + } + + if (mLive) { + mContext.unregisterReceiver(mIntentReceiver); + } + mContext.getContentResolver().unregisterContentObserver( + mFormatChangeObserver); + } + + + void updateTime(Calendar c) { + mCalendar = c; + updateTime(); + } + + private void updateTime() { + if (mLive) { + mCalendar.setTimeInMillis(System.currentTimeMillis()); + } + + CharSequence newTime = DateFormat.format(mFormat, mCalendar); + mTimeDisplay.setText(newTime); + mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0); + } + + private void setDateFormat() { + mFormat = Alarms.get24HourMode(mContext) ? M24 : M12; + mAmPm.setShowAmPm(mFormat == M12); + } + + void setAnimate() { + mAnimate = true; + } + + void setLive(boolean live) { + mLive = live; + } +} diff --git a/src/com/android/alarmclock/Log.java b/src/com/android/alarmclock/Log.java new file mode 100644 index 000000000..18cc391d0 --- /dev/null +++ b/src/com/android/alarmclock/Log.java @@ -0,0 +1,42 @@ +/* + * 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-level logging flag + */ + +package com.android.alarmclock; + +import android.os.SystemClock; +import android.util.Config; + +class Log { + public final static String LOGTAG = "AlarmClock"; + + static final boolean LOGV = AlarmClock.DEBUG ? Config.LOGD : Config.LOGV; + + static void v(String logMe) { + android.util.Log.v(LOGTAG, /* SystemClock.uptimeMillis() + " " + */ logMe); + } + + static void e(String logMe) { + android.util.Log.e(LOGTAG, logMe); + } + + static void e(String logMe, Exception ex) { + android.util.Log.e(LOGTAG, logMe, ex); + } +} diff --git a/src/com/android/alarmclock/RepeatPreference.java b/src/com/android/alarmclock/RepeatPreference.java new file mode 100644 index 000000000..8313858a4 --- /dev/null +++ b/src/com/android/alarmclock/RepeatPreference.java @@ -0,0 +1,76 @@ +/* + * 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.alarmclock; + +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.ListPreference; +import android.util.AttributeSet; + +public class RepeatPreference extends ListPreference { + + private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); + private OnRepeatChangedObserver mOnRepeatChangedObserver; + + public interface OnRepeatChangedObserver { + /** RepeatPrefrence calls this to get initial state */ + public Alarms.DaysOfWeek getDaysOfWeek(); + + /** Called when this preference has changed */ + public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek); + } + + public RepeatPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + void setOnRepeatChangedObserver(OnRepeatChangedObserver onRepeatChangedObserver) { + mOnRepeatChangedObserver = onRepeatChangedObserver; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + mOnRepeatChangedObserver.onRepeatChanged(mDaysOfWeek); + } else { + /* no change -- reset to initial state */ + mDaysOfWeek.set(mOnRepeatChangedObserver.getDaysOfWeek()); + } + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + CharSequence[] entries = getEntries(); + CharSequence[] entryValues = getEntryValues(); + + if (entries == null || entryValues == null) { + throw new IllegalStateException( + "RepeatPreference requires an entries array and an entryValues array."); + } + + mDaysOfWeek.set(mOnRepeatChangedObserver.getDaysOfWeek()); + + builder.setMultiChoiceItems( + entries, mDaysOfWeek.getBooleanArray(), + new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + mDaysOfWeek.set(which, isChecked); + } + }); + } +} diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java new file mode 100644 index 000000000..d8c9b46db --- /dev/null +++ b/src/com/android/alarmclock/SetAlarm.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.alarmclock; + +import android.app.Activity; +import android.app.Dialog; +import android.app.TimePickerDialog; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.CheckBoxPreference; +import android.preference.PreferenceScreen; +import android.text.format.DateFormat; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TimePicker; +import android.widget.Toast; + +/** + * Manages each alarm + */ +public class SetAlarm extends PreferenceActivity + implements Alarms.AlarmSettings, TimePickerDialog.OnTimeSetListener { + + private CheckBoxPreference mAlarmOnPref; + private Preference mTimePref; + private AlarmPreference mAlarmPref; + private CheckBoxPreference mVibratePref; + private RepeatPreference mRepeatPref; + private ContentObserver mAlarmsChangeObserver; + private MenuItem mDeleteAlarmItem; + private MenuItem mTestAlarmItem; + + private int mId; + private int mHour; + private int mMinutes; + private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); + + private boolean mReportAlarmCalled; + + private static final int DIALOG_TIMEPICKER = 0; + + private class RingtoneChangedListener implements AlarmPreference.IRingtoneChangedListener { + public void onRingtoneChanged(Uri ringtoneUri) { + saveAlarm(false); + } + } + + private class OnRepeatChangedObserver implements RepeatPreference.OnRepeatChangedObserver { + public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek) { + if (!mDaysOfWeek.equals(daysOfWeek)) { + mDaysOfWeek.set(daysOfWeek); + saveAlarm(true); + } + } + public Alarms.DaysOfWeek getDaysOfWeek() { + return mDaysOfWeek; + } + } + + private class AlarmsChangeObserver extends ContentObserver { + public AlarmsChangeObserver() { + super(new Handler()); + } + @Override + public void onChange(boolean selfChange) { + Alarms.getAlarm(getContentResolver(), SetAlarm.this, mId); + } + } + + /** + * Set an alarm. Requires an Alarms.ID to be passed in as an + * extra + */ + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.alarm_prefs); + mAlarmOnPref = (CheckBoxPreference)findPreference("on"); + mTimePref = findPreference("time"); + mAlarmPref = (AlarmPreference) findPreference("alarm"); + mVibratePref = (CheckBoxPreference) findPreference("vibrate"); + mRepeatPref = (RepeatPreference) findPreference("setRepeat"); + + Intent i = getIntent(); + mId = i.getIntExtra(Alarms.ID, -1); + if (Log.LOGV) Log.v("In SetAlarm, alarm id = " + mId); + + mReportAlarmCalled = false; + /* load alarm details from database */ + Alarms.getAlarm(getContentResolver(), this, mId); + /* This should never happen, but does occasionally with the monkey. + * I believe it's a race condition where a deleted alarm is opened + * before the alarm list is refreshed. */ + if (!mReportAlarmCalled) { + Log.e("reportAlarm never called!"); + finish(); + } + + mAlarmsChangeObserver = new AlarmsChangeObserver(); + getContentResolver().registerContentObserver( + Alarms.AlarmColumns.CONTENT_URI, true, mAlarmsChangeObserver); + + mAlarmPref.setRingtoneChangedListener(new RingtoneChangedListener()); + mRepeatPref.setOnRepeatChangedObserver(new OnRepeatChangedObserver()); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getContentResolver().unregisterContentObserver(mAlarmsChangeObserver); + } + + @Override + protected Dialog onCreateDialog(int id) { + Dialog d; + + switch (id) { + case DIALOG_TIMEPICKER: + d = new TimePickerDialog( + SetAlarm.this, + this, + 0, + 0, + DateFormat.is24HourFormat(SetAlarm.this)); + d.setTitle(getResources().getString(R.string.time)); + break; + default: + d = null; + } + + return d; + } + + @Override + protected void onPrepareDialog(int id, Dialog dialog) { + super.onPrepareDialog(id, dialog); + + switch (id) { + case DIALOG_TIMEPICKER: + TimePickerDialog timePicker = (TimePickerDialog)dialog; + timePicker.updateTime(mHour, mMinutes); + break; + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + + if (preference == mTimePref) { + showDialog(DIALOG_TIMEPICKER); + } else if (preference == mAlarmOnPref) { + saveAlarm(true); + } else if (preference == mVibratePref) { + saveAlarm(false); + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + mHour = hourOfDay; + mMinutes = minute; + mAlarmOnPref.setChecked(true); + saveAlarm(true); + } + + /** + * Alarms.AlarmSettings implementation. Database feeds current + * settings in through this call + */ + public void reportAlarm( + int idx, boolean enabled, int hour, int minutes, + Alarms.DaysOfWeek daysOfWeek, boolean vibrate,String message, + String alert) { + + mHour = hour; + mMinutes = minutes; + mAlarmOnPref.setChecked(enabled); + mDaysOfWeek.set(daysOfWeek); + mVibratePref.setChecked(vibrate); + + if (alert == null || alert.length() == 0) { + if (Log.LOGV) Log.v("****** reportAlarm null or 0-length alert"); + mAlarmPref.mAlert = getDefaultAlarm(); + if (mAlarmPref.mAlert == null) { + Log.e("****** Default Alarm null"); + } + } else { + mAlarmPref.mAlert = Uri.parse(alert); + if (mAlarmPref.mAlert == null) { + Log.e("****** Parsed null alarm. URI: " + alert); + } + } + if (Log.LOGV) Log.v("****** reportAlarm uri " + alert + " alert " + + mAlarmPref.mAlert); + updateTime(); + updateRepeat(); + updateAlarm(mAlarmPref.mAlert); + + mReportAlarmCalled = true; + } + + /** + * picks the first alarm available + */ + private Uri getDefaultAlarm() { + RingtoneManager ringtoneManager = new RingtoneManager(this); + ringtoneManager.setType(RingtoneManager.TYPE_ALARM); + return ringtoneManager.getRingtoneUri(0); + } + + private void updateTime() { + if (Log.LOGV) Log.v("updateTime " + mId); + mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, mDaysOfWeek)); + } + + private void updateAlarm(Uri ringtoneUri) { + if (Log.LOGV) Log.v("updateAlarm " + mId); + Ringtone ringtone = RingtoneManager.getRingtone(SetAlarm.this, ringtoneUri); + if (ringtone != null) { + mAlarmPref.setSummary(ringtone.getTitle(SetAlarm.this)); + } + } + + private void updateRepeat() { + if (Log.LOGV) Log.v("updateRepeat " + mId); + mRepeatPref.setSummary(mDaysOfWeek.toString(this, true)); + } + + private void saveAlarm(boolean popToast) { + if (mReportAlarmCalled && mAlarmPref.mAlert != null) { + String alertString = mAlarmPref.mAlert.toString(); + saveAlarm(this, mId, mAlarmOnPref.isChecked(), mHour, mMinutes, + mDaysOfWeek, mVibratePref.isChecked(), alertString, + popToast); + } + } + + /** + * Write alarm out to persistent store and pops toast if alarm + * enabled + */ + private static void saveAlarm( + Context context, int id, boolean enabled, int hour, int minute, + Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String alert, + boolean popToast) { + if (Log.LOGV) Log.v("** saveAlarm " + id + " " + enabled + " " + hour + + " " + minute + " vibe " + vibrate); + + // Fix alert string first + Alarms.setAlarm(context, id, enabled, hour, minute, daysOfWeek, vibrate, + "", alert); + + if (enabled && popToast) { + popAlarmSetToast(context, hour, minute, daysOfWeek); + } + } + + /** + * Display a toast that tells the user how long until the alarm + * goes off. This helps prevent "am/pm" mistakes. + */ + static void popAlarmSetToast(Context context, int hour, int minute, + Alarms.DaysOfWeek daysOfWeek) { + + String toastText = formatToast(context, hour, minute, daysOfWeek); + Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); + ToastMaster.setToast(toast); + toast.show(); + } + + /** + * format "Alarm set for 2 days 7 hours and 53 minutes from + * now" + */ + static String formatToast(Context context, int hour, int minute, + Alarms.DaysOfWeek daysOfWeek) { + long alarm = Alarms.calculateAlarm(hour, minute, + daysOfWeek).getTimeInMillis(); + long delta = alarm - System.currentTimeMillis();; + long hours = delta / (1000 * 60 * 60); + long minutes = delta / (1000 * 60) % 60; + long days = hours / 24; + hours = hours % 24; + + String daySeq = (days == 0) ? "" : + (days == 1) ? context.getString(R.string.day) : + context.getString(R.string.days, Long.toString(days)); + + String minSeq = (minutes == 0) ? "" : + (minutes == 1) ? context.getString(R.string.minute) : + context.getString(R.string.minutes, Long.toString(minutes)); + + String hourSeq = (hours == 0) ? "" : + (hours == 1) ? context.getString(R.string.hour) : + context.getString(R.string.hours, Long.toString(hours)); + + boolean dispDays = days > 0; + boolean dispHour = hours > 0; + boolean dispMinute = minutes > 0; + + String ret; + if (!(dispDays || dispHour || dispMinute)) { + ret = context.getString(R.string.subminute); + } else { + String parts[] = new String[5]; + parts[0] = daySeq; + parts[1] = !dispDays ? "" : + dispHour && dispMinute ? context.getString(R.string.space) : + !dispHour && !dispMinute ? "" : + context.getString(R.string.and); + parts[2] = dispHour ? hourSeq : ""; + parts[3] = dispHour && dispMinute ? context.getString(R.string.and) : ""; + parts[4] = dispMinute ? minSeq : ""; + ret = context.getString(R.string.combiner, (Object[])parts); + } + + ret = context.getString(R.string.alarm_set, ret); + /* if (Log.LOGV) Log.v("** TOAST daySeq " + daySeq + " hourSeq " + hourSeq + + " minSeq " + minSeq + " ret " + ret); */ + return ret; + } + + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + mDeleteAlarmItem = menu.add(0, 0, 0, R.string.delete_alarm); + mDeleteAlarmItem.setIcon(android.R.drawable.ic_menu_delete); + + if (AlarmClock.DEBUG) { + mTestAlarmItem = menu.add(0, 0, 0, "test alarm"); + } + + + return true; + } + + public boolean onOptionsItemSelected(MenuItem item) { + if (item == mDeleteAlarmItem) { + Alarms.deleteAlarm(this, mId); + finish(); + return true; + } + if (AlarmClock.DEBUG) { + if (item == mTestAlarmItem) { + setTestAlarm(); + return true; + } + } + + return false; + } + + + /** + * Test code: this is disabled for production build. Sets + * this alarm to go off on the next minute + */ + void setTestAlarm() { + + // start with now + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTimeInMillis(System.currentTimeMillis()); + + int nowHour = c.get(java.util.Calendar.HOUR_OF_DAY); + int nowMinute = c.get(java.util.Calendar.MINUTE); + + int minutes = (nowMinute + 1) % 60; + int hour = nowHour + (nowMinute == 0? 1 : 0); + + saveAlarm(this, mId, true, hour, minutes, mDaysOfWeek, true, + mAlarmPref.mAlert.toString(), true); + } + +} diff --git a/src/com/android/alarmclock/ToastMaster.java b/src/com/android/alarmclock/ToastMaster.java new file mode 100644 index 000000000..c4c281521 --- /dev/null +++ b/src/com/android/alarmclock/ToastMaster.java @@ -0,0 +1,41 @@ +/* + * 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.alarmclock; + +import android.widget.Toast; + +public class ToastMaster { + + private static Toast sToast = null; + + private ToastMaster() { + + } + + public static void setToast(Toast toast) { + if (sToast != null) + sToast.cancel(); + sToast = toast; + } + + public static void cancelToast() { + if (sToast != null) + sToast.cancel(); + sToast = null; + } + +} -- cgit v1.2.3 From 185d17974729a98cf48a71ab9f16adaab9d1e1e0 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Thu, 5 Mar 2009 14:34:37 -0800 Subject: auto import from //depot/cupcake/@136594 --- src/com/android/alarmclock/AlarmAlert.java | 19 ++++++++++-- src/com/android/alarmclock/AlarmClock.java | 22 +++++++++++++- src/com/android/alarmclock/AlarmReceiver.java | 1 + src/com/android/alarmclock/Alarms.java | 30 ++++++++++++++----- src/com/android/alarmclock/SetAlarm.java | 43 +++++++++++++++++++++------ 5 files changed, 96 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index eefd5bb91..2464c9699 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -54,6 +54,7 @@ public class AlarmAlert extends Activity { private AlarmKlaxon mKlaxon; private int mAlarmId; + private String mLabel; @Override protected void onCreate(Bundle icicle) { @@ -81,7 +82,11 @@ public class AlarmAlert extends Activity { mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); - mAlarmId = getIntent().getIntExtra(Alarms.ID, -1); + Intent i = getIntent(); + mAlarmId = i.getIntExtra(Alarms.ID, -1); + + /* Set the title from the passed in label */ + setTitleFromIntent(i); /* allow next alarm to trigger while this activity is active */ @@ -108,6 +113,14 @@ public class AlarmAlert extends Activity { updateLayout(); } + private void setTitleFromIntent(Intent i) { + mLabel = i.getStringExtra(Alarms.LABEL); + if (mLabel == null || mLabel.length() == 0) { + mLabel = getString(R.string.default_label); + } + setTitle(mLabel); + } + private void updateSilencedText() { TextView silenced = (TextView) findViewById(R.id.silencedText); silenced.setText(getString(R.string.alarm_alert_alert_silenced, @@ -181,7 +194,8 @@ public class AlarmAlert extends Activity { Alarms.formatTime(AlarmAlert.this, c)); mState = DISMISS; } else { - Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTime); + Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTime, + mLabel); Alarms.setNextAlert(AlarmAlert.this); displayTime = getString(R.string.alarm_alert_snooze_set, SNOOZE_MINUTES); @@ -218,6 +232,7 @@ public class AlarmAlert extends Activity { disableKeyguard(); mAlarmId = intent.getIntExtra(Alarms.ID, -1); + setTitleFromIntent(intent); /* unset silenced message */ TextView silenced = (TextView)findViewById(R.id.silencedText); diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index 6230d16a4..ca1c9005d 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -104,6 +104,8 @@ public class AlarmClock extends Activity { final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek( cursor.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); final boolean enabled = cursor.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1; + final String label = + cursor.getString(Alarms.AlarmColumns.ALARM_MESSAGE_INDEX); CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton); onButton.setChecked(enabled); @@ -143,8 +145,26 @@ public class AlarmClock extends Activity { c.set(Calendar.HOUR_OF_DAY, hour); c.set(Calendar.MINUTE, minutes); digitalClock.updateTime(c); + + // Set the repeat text or leave it blank if it does not repeat. TextView daysOfWeekView = (TextView) digitalClock.findViewById(R.id.daysOfWeek); - daysOfWeekView.setText(daysOfWeek.toString(AlarmClock.this, false)); + final String daysOfWeekStr = + daysOfWeek.toString(AlarmClock.this, false); + if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) { + daysOfWeekView.setText(daysOfWeekStr); + daysOfWeekView.setVisibility(View.VISIBLE); + } else { + daysOfWeekView.setVisibility(View.GONE); + } + + // Display the label + TextView labelView = + (TextView) digitalClock.findViewById(R.id.label); + if (label != null && label.length() != 0) { + labelView.setText(label); + } else { + labelView.setText(R.string.default_label); + } // Build context menu digitalClock.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index af18b7b7a..113795290 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -62,6 +62,7 @@ public class AlarmReceiver extends BroadcastReceiver { * so that the current app's notification management is not disturbed */ Intent fireAlarm = new Intent(context, AlarmAlert.class); fireAlarm.putExtra(Alarms.ID, id); + fireAlarm.putExtra(Alarms.LABEL, intent.getStringExtra(Alarms.LABEL)); fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); context.startActivity(fireAlarm); } diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index 56af9b310..881723493 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -42,9 +42,11 @@ public class Alarms { public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; public final static String ID = "alarm_id"; public final static String TIME = "alarm_time"; + public final static String LABEL = "alarm_label"; final static String PREF_SNOOZE_ID = "snooze_id"; final static String PREF_SNOOZE_TIME = "snooze_time"; + final static String PREF_SNOOZE_LABEL = "snooze_label"; private final static String DM12 = "E h:mm aa"; private final static String DM24 = "E k:mm"; @@ -480,8 +482,9 @@ public class Alarms { * Calculates next scheduled alert */ static class AlarmCalculator implements AlarmSettings { - public long mMinAlert = Long.MAX_VALUE; - public int mMinIdx = -1; + private long mMinAlert = Long.MAX_VALUE; + private int mMinIdx = -1; + private String mLabel; /** * returns next scheduled alert, MAX_VALUE if none @@ -492,6 +495,9 @@ public class Alarms { public int getIndex() { return mMinIdx; } + public String getLabel() { + return mLabel; + } public void reportAlarm( int idx, boolean enabled, int hour, int minutes, @@ -505,6 +511,7 @@ public class Alarms { if (atTime < mMinAlert) { mMinIdx = idx; mMinAlert = atTime; + mLabel = message; } } } @@ -564,7 +571,7 @@ public class Alarms { long atTime = ac.getAlert(); if (atTime < Long.MAX_VALUE) { - enableAlert(context, id, atTime); + enableAlert(context, id, ac.getLabel(), atTime); } else { disableAlert(context, id); } @@ -586,13 +593,15 @@ public class Alarms { * @param id Alarm ID. * @param atTimeInMillis milliseconds since epoch */ - static void enableAlert(Context context, int id, long atTimeInMillis) { + static void enableAlert(Context context, int id, String label, + long atTimeInMillis) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(ALARM_ALERT_ACTION); if (Log.LOGV) Log.v("** setAlert id " + id + " atTime " + atTimeInMillis); intent.putExtra(ID, id); + intent.putExtra(LABEL, label); intent.putExtra(TIME, atTimeInMillis); PendingIntent sender = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); @@ -631,12 +640,13 @@ public class Alarms { } static void saveSnoozeAlert(final Context context, int id, - long atTimeInMillis) { + long atTimeInMillis, String label) { SharedPreferences prefs = context.getSharedPreferences( AlarmClock.PREFERENCES, 0); SharedPreferences.Editor ed = prefs.edit(); ed.putInt(PREF_SNOOZE_ID, id); ed.putLong(PREF_SNOOZE_TIME, atTimeInMillis); + ed.putString(PREF_SNOOZE_LABEL, label); ed.commit(); } @@ -646,7 +656,7 @@ public class Alarms { static int disableSnoozeAlert(final Context context) { int id = getSnoozeAlarmId(context); if (id == -1) return -1; - saveSnoozeAlert(context, -1, 0); + saveSnoozeAlert(context, -1, 0, null); return id; } @@ -671,7 +681,13 @@ public class Alarms { if (id == -1) return false; long atTimeInMillis = prefs.getLong(PREF_SNOOZE_TIME, -1); if (id == -1) return false; - enableAlert(context, id, atTimeInMillis); + // Try to get the label from the snooze preference. If null, use the + // default label. + String label = prefs.getString(PREF_SNOOZE_LABEL, null); + if (label == null) { + label = context.getString(R.string.default_label); + } + enableAlert(context, id, label, atTimeInMillis); return true; } diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index d8c9b46db..807b469df 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -27,9 +27,10 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; import android.preference.Preference; import android.preference.PreferenceActivity; -import android.preference.CheckBoxPreference; import android.preference.PreferenceScreen; import android.text.format.DateFormat; import android.view.Menu; @@ -43,6 +44,7 @@ import android.widget.Toast; public class SetAlarm extends PreferenceActivity implements Alarms.AlarmSettings, TimePickerDialog.OnTimeSetListener { + private EditTextPreference mLabel; private CheckBoxPreference mAlarmOnPref; private Preference mTimePref; private AlarmPreference mAlarmPref; @@ -98,6 +100,16 @@ public class SetAlarm extends PreferenceActivity super.onCreate(icicle); addPreferencesFromResource(R.xml.alarm_prefs); + mLabel = (EditTextPreference) findPreference("label"); + mLabel.setOnPreferenceChangeListener( + new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference p, + Object newValue) { + p.setSummary((String) newValue); + saveAlarm(false, (String) newValue); + return true; + } + }); mAlarmOnPref = (CheckBoxPreference)findPreference("on"); mTimePref = findPreference("time"); mAlarmPref = (AlarmPreference) findPreference("alarm"); @@ -193,9 +205,14 @@ public class SetAlarm extends PreferenceActivity */ public void reportAlarm( int idx, boolean enabled, int hour, int minutes, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate,String message, + Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String label, String alert) { + if (label == null || label.length() == 0) { + label = getString(R.string.default_label); + } + mLabel.setText(label); + mLabel.setSummary(label); mHour = hour; mMinutes = minutes; mAlarmOnPref.setChecked(enabled); @@ -251,10 +268,18 @@ public class SetAlarm extends PreferenceActivity } private void saveAlarm(boolean popToast) { + saveAlarm(popToast, mLabel.getText()); + } + + /** + * This version of saveAlarm uses the passed in label since mLabel may + * contain the old value (i.e. during the preference value change). + */ + private void saveAlarm(boolean popToast, String label) { if (mReportAlarmCalled && mAlarmPref.mAlert != null) { String alertString = mAlarmPref.mAlert.toString(); saveAlarm(this, mId, mAlarmOnPref.isChecked(), mHour, mMinutes, - mDaysOfWeek, mVibratePref.isChecked(), alertString, + mDaysOfWeek, mVibratePref.isChecked(), label, alertString, popToast); } } @@ -265,14 +290,14 @@ public class SetAlarm extends PreferenceActivity */ private static void saveAlarm( Context context, int id, boolean enabled, int hour, int minute, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String alert, - boolean popToast) { - if (Log.LOGV) Log.v("** saveAlarm " + id + " " + enabled + " " + hour + - " " + minute + " vibe " + vibrate); + Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String label, + String alert, boolean popToast) { + if (Log.LOGV) Log.v("** saveAlarm " + id + " " + label + " " + enabled + + " " + hour + " " + minute + " vibe " + vibrate); // Fix alert string first Alarms.setAlarm(context, id, enabled, hour, minute, daysOfWeek, vibrate, - "", alert); + label, alert); if (enabled && popToast) { popAlarmSetToast(context, hour, minute, daysOfWeek); @@ -392,7 +417,7 @@ public class SetAlarm extends PreferenceActivity int hour = nowHour + (nowMinute == 0? 1 : 0); saveAlarm(this, mId, true, hour, minutes, mDaysOfWeek, true, - mAlarmPref.mAlert.toString(), true); + mLabel.getText(), mAlarmPref.mAlert.toString(), true); } } -- cgit v1.2.3 From bcdf0e36db34f2875fd1df75826043e75c6cdeb0 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 11 Mar 2009 12:11:58 -0700 Subject: auto import from //branches/cupcake/...@137873 --- .../alarmclock/AnalogAppWidgetProvider.java | 66 ++++++++++++++++++++++ .../android/alarmclock/AnalogGadgetProvider.java | 66 ---------------------- 2 files changed, 66 insertions(+), 66 deletions(-) create mode 100644 src/com/android/alarmclock/AnalogAppWidgetProvider.java delete mode 100644 src/com/android/alarmclock/AnalogGadgetProvider.java (limited to 'src') diff --git a/src/com/android/alarmclock/AnalogAppWidgetProvider.java b/src/com/android/alarmclock/AnalogAppWidgetProvider.java new file mode 100644 index 000000000..524b1d22c --- /dev/null +++ b/src/com/android/alarmclock/AnalogAppWidgetProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2009 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.alarmclock; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.provider.Calendar; +import android.provider.Calendar.Attendees; +import android.provider.Calendar.Calendars; +import android.provider.Calendar.EventsColumns; +import android.provider.Calendar.Instances; +import android.provider.Calendar.Reminders; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Config; +import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; + +import java.util.Arrays; + +/** + * Simple widget to show analog clock. + */ +public class AnalogAppWidgetProvider extends BroadcastReceiver { + static final String TAG = "AnalogAppWidgetProvider"; + + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { + RemoteViews views = new RemoteViews(context.getPackageName(), + R.layout.analog_appwidget); + + int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + + AppWidgetManager gm = AppWidgetManager.getInstance(context); + gm.updateAppWidget(appWidgetIds, views); + } + } +} + diff --git a/src/com/android/alarmclock/AnalogGadgetProvider.java b/src/com/android/alarmclock/AnalogGadgetProvider.java deleted file mode 100644 index 1143da586..000000000 --- a/src/com/android/alarmclock/AnalogGadgetProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2009 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.alarmclock; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.gadget.GadgetManager; -import android.graphics.PorterDuff; -import android.net.Uri; -import android.provider.Calendar; -import android.provider.Calendar.Attendees; -import android.provider.Calendar.Calendars; -import android.provider.Calendar.EventsColumns; -import android.provider.Calendar.Instances; -import android.provider.Calendar.Reminders; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.util.Config; -import android.util.Log; -import android.view.View; -import android.widget.RemoteViews; - -import java.util.Arrays; - -/** - * Simple gadget to show analog clock. - */ -public class AnalogGadgetProvider extends BroadcastReceiver { - static final String TAG = "AnalogGadgetProvider"; - - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (GadgetManager.ACTION_GADGET_UPDATE.equals(action)) { - RemoteViews views = new RemoteViews(context.getPackageName(), - R.layout.analog_gadget); - - int[] gadgetIds = intent.getIntArrayExtra(GadgetManager.EXTRA_GADGET_IDS); - - GadgetManager gm = GadgetManager.getInstance(context); - gm.updateGadget(gadgetIds, views); - } - } -} - -- cgit v1.2.3 From 72a37ccef83271f175c94b71f2d0abac8b4aefa4 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Fri, 13 Mar 2009 13:04:24 -0700 Subject: auto import from //branches/cupcake_rel/...@138607 --- src/com/android/alarmclock/AlarmKlaxon.java | 97 +++++++++++++++++------------ 1 file changed, 56 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index c862292dc..5773acfce 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -19,6 +19,7 @@ package com.android.alarmclock; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.content.res.Resources; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnErrorListener; @@ -26,6 +27,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Vibrator; +import android.telephony.TelephonyManager; /** * Manages alarms and vibe. Singleton, so it can be initiated in @@ -78,6 +80,9 @@ class AlarmKlaxon implements Alarms.AlarmSettings { mVibrate = vibrate; } + // Volume suggested by media team for in-call alarms. + private static final float IN_CALL_VOLUME = 0.125f; + synchronized void play(Context context, int alarmId) { ContentResolver contentResolver = context.getContentResolver(); @@ -90,52 +95,53 @@ class AlarmKlaxon implements Alarms.AlarmSettings { if (Log.LOGV) Log.v("AlarmKlaxon.play() " + mAlarmId + " alert " + mAlert); - /* play audio alert */ - if (mAlert == null) { - Log.e("Unable to play alarm: no audio file available"); - } else { - /* we need a new MediaPlayer when we change media URLs */ - mMediaPlayer = new MediaPlayer(); - mMediaPlayer.setOnErrorListener(new OnErrorListener() { - public boolean onError(MediaPlayer mp, int what, int extra) { - Log.e("Error occurred while playing audio."); - mp.stop(); - mp.release(); - mMediaPlayer = null; - return true; - } - }); - - try { + // TODO: Reuse mMediaPlayer instead of creating a new one and/or use + // RingtoneManager. + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnErrorListener(new OnErrorListener() { + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e("Error occurred while playing audio."); + mp.stop(); + mp.release(); + mMediaPlayer = null; + return true; + } + }); + + try { + TelephonyManager tm = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + // Check if we are in a call. If we are, use the in-call alarm + // resource at a low volume to not disrupt the call. + if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) { + Log.v("Using the in-call alarm"); + mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); + setDataSourceFromResource(context.getResources(), + mMediaPlayer, R.raw.in_call_alarm); + } else { mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); - } catch (Exception ex) { - Log.v("Using the fallback ringtone"); - /* The alert may be on the sd card which could be busy right - * now. Use the fallback ringtone. */ - AssetFileDescriptor afd = - context.getResources().openRawResourceFd( - com.android.internal.R.raw.fallbackring); - if (afd != null) { - try { - mMediaPlayer.setDataSource(afd.getFileDescriptor(), - afd.getStartOffset(), afd.getLength()); - afd.close(); - } catch (Exception ex2) { - Log.e("Failed to play fallback ringtone", ex2); - /* At this point we just don't play anything */ - } - } } - /* Now try to play the alert. */ + } catch (Exception ex) { + Log.v("Using the fallback ringtone"); + // The alert may be on the sd card which could be busy right now. + // Use the fallback ringtone. try { - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mMediaPlayer.setLooping(true); - mMediaPlayer.prepare(); - mMediaPlayer.start(); - } catch (Exception ex) { - Log.e("Error playing alarm: " + mAlert, ex); + setDataSourceFromResource(context.getResources(), mMediaPlayer, + com.android.internal.R.raw.fallbackring); + } catch (Exception ex2) { + // At this point we just don't play anything. + Log.e("Failed to play fallback ringtone", ex2); } } + /* Now try to play the alert. */ + try { + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + mMediaPlayer.setLooping(true); + mMediaPlayer.prepare(); + mMediaPlayer.start(); + } catch (Exception ex) { + Log.e("Error playing alarm: " + mAlert, ex); + } /* Start the vibrator after everything is ok with the media player */ if (mVibrate) { @@ -148,6 +154,15 @@ class AlarmKlaxon implements Alarms.AlarmSettings { mPlaying = true; } + private void setDataSourceFromResource(Resources resources, + MediaPlayer player, int res) throws java.io.IOException { + AssetFileDescriptor afd = resources.openRawResourceFd(res); + if (afd != null) { + player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), + afd.getLength()); + afd.close(); + } + } /** * Stops alarm audio and disables alarm if it not snoozed and not -- cgit v1.2.3 From 69dabe13f25d6a0cd98041eb2b88f24701e0ce5b Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 18 Mar 2009 17:39:47 -0700 Subject: auto import from //branches/cupcake_rel/...@140373 --- src/com/android/alarmclock/AlarmAlert.java | 15 ++++-- src/com/android/alarmclock/AlarmAlertWakeLock.java | 6 ++- src/com/android/alarmclock/AlarmKlaxon.java | 62 ++++++++++++---------- src/com/android/alarmclock/AlarmReceiver.java | 7 +-- 4 files changed, 52 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 2464c9699..6a9a71879 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -48,7 +48,7 @@ public class AlarmAlert extends Activity { private static final int KILLED = 3; private KeyguardManager mKeyguardManager; - private KeyguardManager.KeyguardLock mKeyguardLock = null; + private KeyguardManager.KeyguardLock mKeyguardLock; private Button mSnoozeButton; private int mState = UNKNOWN; @@ -60,12 +60,15 @@ public class AlarmAlert extends Activity { protected void onCreate(Bundle icicle) { super.onCreate(icicle); + // Maintain a lock during the playback of the alarm. This lock may have + // already been acquired in AlarmReceiver. If the process was killed, + // the global wake lock is gone. Acquire again just to be sure. + AlarmAlertWakeLock.acquire(this); + /* FIXME Intentionally verbose: always log this until we've fully debugged the app failing to start up */ Log.v("AlarmAlert.onCreate()"); - mKlaxon = AlarmKlaxon.getInstance(); - // Popup alert over black screen WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; @@ -85,6 +88,9 @@ public class AlarmAlert extends Activity { Intent i = getIntent(); mAlarmId = i.getIntExtra(Alarms.ID, -1); + mKlaxon = new AlarmKlaxon(); + mKlaxon.postPlay(this, mAlarmId); + /* Set the title from the passed in label */ setTitleFromIntent(i); @@ -232,6 +238,9 @@ public class AlarmAlert extends Activity { disableKeyguard(); mAlarmId = intent.getIntExtra(Alarms.ID, -1); + // Play the new alarm sound. + mKlaxon.postPlay(this, mAlarmId); + setTitleFromIntent(intent); /* unset silenced message */ diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java index 083996e58..10e5b54e4 100644 --- a/src/com/android/alarmclock/AlarmAlertWakeLock.java +++ b/src/com/android/alarmclock/AlarmAlertWakeLock.java @@ -28,11 +28,13 @@ class AlarmAlertWakeLock { private static PowerManager.WakeLock sWakeLock; static void acquire(Context context) { + Log.v("Acquiring wake lock"); if (sWakeLock != null) { sWakeLock.release(); } - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + PowerManager pm = + (PowerManager) context.getSystemService(Context.POWER_SERVICE); sWakeLock = pm.newWakeLock( PowerManager.FULL_WAKE_LOCK | @@ -42,7 +44,7 @@ class AlarmAlertWakeLock { } static void release() { - if (Log.LOGV) Log.v("AlarmAlertWakeLock release"); + Log.v("Releasing wake lock"); if (sWakeLock != null) { sWakeLock.release(); sWakeLock = null; diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index 5773acfce..e75eb80dd 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -26,6 +26,7 @@ import android.media.MediaPlayer.OnErrorListener; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Message; import android.os.Vibrator; import android.telephony.TelephonyManager; @@ -42,30 +43,39 @@ class AlarmKlaxon implements Alarms.AlarmSettings { /** Play alarm up to 10 minutes before silencing */ final static int ALARM_TIMEOUT_SECONDS = 10 * 60; - private static long[] sVibratePattern = new long[] { 500, 500 }; - - private static AlarmKlaxon sInstance; + private static final long[] sVibratePattern = new long[] { 500, 500 }; private int mAlarmId; private String mAlert; private Alarms.DaysOfWeek mDaysOfWeek; private boolean mVibrate; - private boolean mPlaying = false; - private Vibrator mVibrator; private MediaPlayer mMediaPlayer; - - private Handler mTimeout; private KillerCallback mKillerCallback; + // Internal messages + private static final int KILLER = 1000; + private static final int PLAY = 1001; + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case KILLER: + if (Log.LOGV) { + Log.v("*********** Alarm killer triggered ***********"); + } + if (mKillerCallback != null) { + mKillerCallback.onKilled(); + } + break; + case PLAY: + play((Context) msg.obj, msg.arg1); + break; + } + } + }; - static synchronized AlarmKlaxon getInstance() { - if (sInstance == null) sInstance = new AlarmKlaxon(); - return sInstance; - } - - private AlarmKlaxon() { + AlarmKlaxon() { mVibrator = new Vibrator(); } @@ -80,10 +90,14 @@ class AlarmKlaxon implements Alarms.AlarmSettings { mVibrate = vibrate; } - // Volume suggested by media team for in-call alarms. + public void postPlay(final Context context, final int alarmId) { + mHandler.sendMessage(mHandler.obtainMessage(PLAY, alarmId, 0, context)); + } + + // Volume suggested by media team for in-call alarms. private static final float IN_CALL_VOLUME = 0.125f; - synchronized void play(Context context, int alarmId) { + private void play(Context context, int alarmId) { ContentResolver contentResolver = context.getContentResolver(); if (mPlaying) stop(context, false); @@ -168,7 +182,7 @@ class AlarmKlaxon implements Alarms.AlarmSettings { * Stops alarm audio and disables alarm if it not snoozed and not * repeating */ - synchronized void stop(Context context, boolean snoozed) { + public void stop(Context context, boolean snoozed) { if (Log.LOGV) Log.v("AlarmKlaxon.stop() " + mAlarmId); if (mPlaying) { mPlaying = false; @@ -195,7 +209,7 @@ class AlarmKlaxon implements Alarms.AlarmSettings { * This callback called when alarm killer times out unattended * alarm */ - void setKillerCallback(KillerCallback killerCallback) { + public void setKillerCallback(KillerCallback killerCallback) { mKillerCallback = killerCallback; } @@ -207,20 +221,12 @@ class AlarmKlaxon implements Alarms.AlarmSettings { * popped, so the user will know that the alarm tripped. */ private void enableKiller() { - mTimeout = new Handler(); - mTimeout.postDelayed(new Runnable() { - public void run() { - if (Log.LOGV) Log.v("*********** Alarm killer triggered *************"); - if (mKillerCallback != null) mKillerCallback.onKilled(); - } - }, 1000 * ALARM_TIMEOUT_SECONDS); + mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER), + 1000 * ALARM_TIMEOUT_SECONDS); } private void disableKiller() { - if (mTimeout != null) { - mTimeout.removeCallbacksAndMessages(null); - mTimeout = null; - } + mHandler.removeMessages(KILLER); } diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 113795290..4982f95b8 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -47,13 +47,10 @@ public class AlarmReceiver extends BroadcastReceiver { return; } - /* wake device */ + /* Wake the device and stay awake until the AlarmAlert intent is + * handled. */ AlarmAlertWakeLock.acquire(context); - /* start audio/vibe */ - AlarmKlaxon klaxon = AlarmKlaxon.getInstance(); - klaxon.play(context, id); - /* Close dialogs and window shade */ Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.sendBroadcast(i); -- cgit v1.2.3 From 57c4e8b2ef600f89caafb0520b44b06fc73ab845 Mon Sep 17 00:00:00 2001 From: Jason Parekh <> Date: Tue, 24 Mar 2009 17:48:25 -0700 Subject: Automated import from //branches/donutburger/...@140507,140507 --- src/com/android/alarmclock/AlarmClock.java | 4 ++ src/com/android/alarmclock/SettingsActivity.java | 88 ++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/com/android/alarmclock/SettingsActivity.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index ca1c9005d..f8902b967 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -281,6 +281,10 @@ public class AlarmClock extends Activity { mToggleClockItem = menu.add(0, 0, 0, R.string.hide_clock); mToggleClockItem.setIcon(R.drawable.ic_menu_clock_face); + + MenuItem settingsItem = menu.add(0, 0, 0, R.string.settings); + settingsItem.setIcon(android.R.drawable.ic_menu_preferences); + settingsItem.setIntent(new Intent(this, SettingsActivity.class)); return true; } diff --git a/src/com/android/alarmclock/SettingsActivity.java b/src/com/android/alarmclock/SettingsActivity.java new file mode 100644 index 000000000..44c4045ff --- /dev/null +++ b/src/com/android/alarmclock/SettingsActivity.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2009 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.alarmclock; + +import android.media.AudioManager; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.provider.Settings; + +/** + * Settings for the Alarm Clock. + */ +public class SettingsActivity extends PreferenceActivity { + + private static final int ALARM_STREAM_TYPE_BIT = + 1 << AudioManager.STREAM_ALARM; + + private static final String KEY_ALARM_IN_SILENT_MODE = + "alarm_in_silent_mode"; + private CheckBoxPreference mAlarmInSilentModePref; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.settings); + + mAlarmInSilentModePref = + (CheckBoxPreference) findPreference(KEY_ALARM_IN_SILENT_MODE); + } + + @Override + protected void onResume() { + super.onResume(); + refresh(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + + if (preference == mAlarmInSilentModePref) { + + int ringerModeStreamTypes = Settings.System.getInt( + getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + if (mAlarmInSilentModePref.isChecked()) { + ringerModeStreamTypes &= ~ALARM_STREAM_TYPE_BIT; + } else { + ringerModeStreamTypes |= ALARM_STREAM_TYPE_BIT; + } + + Settings.System.putInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + ringerModeStreamTypes); + + return true; + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + private void refresh() { + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + mAlarmInSilentModePref.setChecked( + (silentModeStreams & ALARM_STREAM_TYPE_BIT) == 0); + } + +} -- cgit v1.2.3 From 13e902189e05f09a890b9d6673a6f451c758542a Mon Sep 17 00:00:00 2001 From: Jason Parekh <> Date: Tue, 24 Mar 2009 17:50:29 -0700 Subject: Automated import from //branches/master/...@140509,140509 --- src/com/android/alarmclock/AlarmClock.java | 4 ++ src/com/android/alarmclock/SettingsActivity.java | 88 ++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/com/android/alarmclock/SettingsActivity.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index ca1c9005d..f8902b967 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -281,6 +281,10 @@ public class AlarmClock extends Activity { mToggleClockItem = menu.add(0, 0, 0, R.string.hide_clock); mToggleClockItem.setIcon(R.drawable.ic_menu_clock_face); + + MenuItem settingsItem = menu.add(0, 0, 0, R.string.settings); + settingsItem.setIcon(android.R.drawable.ic_menu_preferences); + settingsItem.setIntent(new Intent(this, SettingsActivity.class)); return true; } diff --git a/src/com/android/alarmclock/SettingsActivity.java b/src/com/android/alarmclock/SettingsActivity.java new file mode 100644 index 000000000..44c4045ff --- /dev/null +++ b/src/com/android/alarmclock/SettingsActivity.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2009 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.alarmclock; + +import android.media.AudioManager; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.provider.Settings; + +/** + * Settings for the Alarm Clock. + */ +public class SettingsActivity extends PreferenceActivity { + + private static final int ALARM_STREAM_TYPE_BIT = + 1 << AudioManager.STREAM_ALARM; + + private static final String KEY_ALARM_IN_SILENT_MODE = + "alarm_in_silent_mode"; + private CheckBoxPreference mAlarmInSilentModePref; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.settings); + + mAlarmInSilentModePref = + (CheckBoxPreference) findPreference(KEY_ALARM_IN_SILENT_MODE); + } + + @Override + protected void onResume() { + super.onResume(); + refresh(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + + if (preference == mAlarmInSilentModePref) { + + int ringerModeStreamTypes = Settings.System.getInt( + getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + if (mAlarmInSilentModePref.isChecked()) { + ringerModeStreamTypes &= ~ALARM_STREAM_TYPE_BIT; + } else { + ringerModeStreamTypes |= ALARM_STREAM_TYPE_BIT; + } + + Settings.System.putInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + ringerModeStreamTypes); + + return true; + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + private void refresh() { + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + mAlarmInSilentModePref.setChecked( + (silentModeStreams & ALARM_STREAM_TYPE_BIT) == 0); + } + +} -- cgit v1.2.3 From d58247b4d07d8c84b2f5dfd85667201f111e35d5 Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Tue, 24 Mar 2009 21:14:13 -0700 Subject: Automated import from //branches/donutburger/...@141976,141976 --- src/com/android/alarmclock/AlarmClock.java | 67 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index f8902b967..06ff42c7d 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -48,7 +48,6 @@ import java.util.Calendar; public class AlarmClock extends Activity { final static String PREFERENCES = "AlarmClock"; - final static int SET_ALARM = 1; final static String PREF_CLOCK_FACE = "face"; final static String PREF_SHOW_CLOCK = "show_clock"; @@ -129,7 +128,7 @@ public class AlarmClock extends Activity { if (true) { Intent intent = new Intent(AlarmClock.this, SetAlarm.class); intent.putExtra(Alarms.ID, id); - startActivityForResult(intent, SET_ALARM); + startActivity(intent); } else { // TESTING: immediately pop alarm Intent fireAlarm = new Intent(AlarmClock.this, AlarmAlert.class); @@ -274,19 +273,10 @@ public class AlarmClock extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); + // Inflate our menu. + getMenuInflater().inflate(R.menu.main_menu, menu); - mAddAlarmItem = menu.add(0, 0, 0, R.string.add_alarm); - mAddAlarmItem.setIcon(android.R.drawable.ic_menu_add); - - mToggleClockItem = menu.add(0, 0, 0, R.string.hide_clock); - mToggleClockItem.setIcon(R.drawable.ic_menu_clock_face); - - MenuItem settingsItem = menu.add(0, 0, 0, R.string.settings); - settingsItem.setIcon(android.R.drawable.ic_menu_preferences); - settingsItem.setIntent(new Intent(this, SettingsActivity.class)); - - return true; + return super.onCreateOptionsMenu(menu); } /** @@ -295,32 +285,41 @@ public class AlarmClock extends Activity { */ @Override public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - mAddAlarmItem.setVisible(mAlarmsList.getChildCount() < MAX_ALARM_COUNT); - mToggleClockItem.setTitle(getClockVisibility() ? R.string.hide_clock : - R.string.show_clock); - return true; + menu.findItem(R.id.menu_add_alarm).setVisible( + mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); + menu.findItem(R.id.menu_toggle_clock).setTitle( + getClockVisibility() ? R.string.hide_clock + : R.string.show_clock); + return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item == mAddAlarmItem) { - Uri uri = Alarms.addAlarm(getContentResolver()); - // FIXME: scroll to new item. mAlarmsList.requestChildRectangleOnScreen() ? - String segment = uri.getPathSegments().get(1); - int newId = Integer.parseInt(segment); - if (Log.LOGV) Log.v("In AlarmClock, new alarm id = " + newId); - Intent intent = new Intent(AlarmClock.this, SetAlarm.class); - intent.putExtra(Alarms.ID, newId); - startActivityForResult(intent, SET_ALARM); - return true; - } else if (item == mToggleClockItem) { - setClockVisibility(!getClockVisibility()); - saveClockVisibility(); - return true; + switch (item.getItemId()) { + case R.id.menu_add_alarm: + Uri uri = Alarms.addAlarm(getContentResolver()); + // FIXME: scroll to new item? + String segment = uri.getPathSegments().get(1); + int newId = Integer.parseInt(segment); + if (Log.LOGV) { + Log.v("In AlarmClock, new alarm id = " + newId); + } + Intent intent = new Intent(this, SetAlarm.class); + intent.putExtra(Alarms.ID, newId); + startActivity(intent); + return true; + + case R.id.menu_toggle_clock: + setClockVisibility(!getClockVisibility()); + saveClockVisibility(); + return true; + + case R.id.menu_settings: + startActivity(new Intent(this, SettingsActivity.class)); + return true; } - return false; + return super.onOptionsItemSelected(item); } -- cgit v1.2.3 From 06522bf515aa117255e1e1d7ad0d6ae56a55ebfc Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Tue, 24 Mar 2009 21:18:07 -0700 Subject: Automated import from //branches/master/...@141978,141978 --- src/com/android/alarmclock/AlarmClock.java | 67 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index f8902b967..06ff42c7d 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -48,7 +48,6 @@ import java.util.Calendar; public class AlarmClock extends Activity { final static String PREFERENCES = "AlarmClock"; - final static int SET_ALARM = 1; final static String PREF_CLOCK_FACE = "face"; final static String PREF_SHOW_CLOCK = "show_clock"; @@ -129,7 +128,7 @@ public class AlarmClock extends Activity { if (true) { Intent intent = new Intent(AlarmClock.this, SetAlarm.class); intent.putExtra(Alarms.ID, id); - startActivityForResult(intent, SET_ALARM); + startActivity(intent); } else { // TESTING: immediately pop alarm Intent fireAlarm = new Intent(AlarmClock.this, AlarmAlert.class); @@ -274,19 +273,10 @@ public class AlarmClock extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); + // Inflate our menu. + getMenuInflater().inflate(R.menu.main_menu, menu); - mAddAlarmItem = menu.add(0, 0, 0, R.string.add_alarm); - mAddAlarmItem.setIcon(android.R.drawable.ic_menu_add); - - mToggleClockItem = menu.add(0, 0, 0, R.string.hide_clock); - mToggleClockItem.setIcon(R.drawable.ic_menu_clock_face); - - MenuItem settingsItem = menu.add(0, 0, 0, R.string.settings); - settingsItem.setIcon(android.R.drawable.ic_menu_preferences); - settingsItem.setIntent(new Intent(this, SettingsActivity.class)); - - return true; + return super.onCreateOptionsMenu(menu); } /** @@ -295,32 +285,41 @@ public class AlarmClock extends Activity { */ @Override public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - mAddAlarmItem.setVisible(mAlarmsList.getChildCount() < MAX_ALARM_COUNT); - mToggleClockItem.setTitle(getClockVisibility() ? R.string.hide_clock : - R.string.show_clock); - return true; + menu.findItem(R.id.menu_add_alarm).setVisible( + mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); + menu.findItem(R.id.menu_toggle_clock).setTitle( + getClockVisibility() ? R.string.hide_clock + : R.string.show_clock); + return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item == mAddAlarmItem) { - Uri uri = Alarms.addAlarm(getContentResolver()); - // FIXME: scroll to new item. mAlarmsList.requestChildRectangleOnScreen() ? - String segment = uri.getPathSegments().get(1); - int newId = Integer.parseInt(segment); - if (Log.LOGV) Log.v("In AlarmClock, new alarm id = " + newId); - Intent intent = new Intent(AlarmClock.this, SetAlarm.class); - intent.putExtra(Alarms.ID, newId); - startActivityForResult(intent, SET_ALARM); - return true; - } else if (item == mToggleClockItem) { - setClockVisibility(!getClockVisibility()); - saveClockVisibility(); - return true; + switch (item.getItemId()) { + case R.id.menu_add_alarm: + Uri uri = Alarms.addAlarm(getContentResolver()); + // FIXME: scroll to new item? + String segment = uri.getPathSegments().get(1); + int newId = Integer.parseInt(segment); + if (Log.LOGV) { + Log.v("In AlarmClock, new alarm id = " + newId); + } + Intent intent = new Intent(this, SetAlarm.class); + intent.putExtra(Alarms.ID, newId); + startActivity(intent); + return true; + + case R.id.menu_toggle_clock: + setClockVisibility(!getClockVisibility()); + saveClockVisibility(); + return true; + + case R.id.menu_settings: + startActivity(new Intent(this, SettingsActivity.class)); + return true; } - return false; + return super.onOptionsItemSelected(item); } -- cgit v1.2.3 From 22b87172ef05dd21860ea193eaca38ef3f83ec11 Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Tue, 24 Mar 2009 21:22:12 -0700 Subject: Automated import from //branches/donutburger/...@142048,142048 --- src/com/android/alarmclock/AlarmAlert.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 6a9a71879..a89a88437 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -264,6 +264,9 @@ public class AlarmAlert extends Activity { if (Log.LOGV) Log.v("AlarmAlert.onStop()"); // As a last resort, try to snooze if this activity is stopped. snooze(); + // We might have been killed by the KillerCallback so always release + // the lock and keyguard. + releaseLocks(); } @Override -- cgit v1.2.3 From fca1632fc438f5ecc2065a30bab2bb374d3667c5 Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Tue, 24 Mar 2009 21:26:48 -0700 Subject: Automated import from //branches/master/...@142049,142049 --- src/com/android/alarmclock/AlarmAlert.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 6a9a71879..a89a88437 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -264,6 +264,9 @@ public class AlarmAlert extends Activity { if (Log.LOGV) Log.v("AlarmAlert.onStop()"); // As a last resort, try to snooze if this activity is stopped. snooze(); + // We might have been killed by the KillerCallback so always release + // the lock and keyguard. + releaseLocks(); } @Override -- cgit v1.2.3 From f9714f85282275737fa6398313344a24dc12a789 Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Tue, 24 Mar 2009 22:34:45 -0700 Subject: Automated import from //branches/donutburger/...@142356,142356 --- src/com/android/alarmclock/AlarmKlaxon.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index e75eb80dd..0e42daa65 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -121,6 +121,8 @@ class AlarmKlaxon implements Alarms.AlarmSettings { return true; } }); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + mMediaPlayer.setLooping(true); try { TelephonyManager tm = (TelephonyManager) context.getSystemService( @@ -135,6 +137,8 @@ class AlarmKlaxon implements Alarms.AlarmSettings { } else { mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); } + mMediaPlayer.prepare(); + mMediaPlayer.start(); } catch (Exception ex) { Log.v("Using the fallback ringtone"); // The alert may be on the sd card which could be busy right now. @@ -142,20 +146,13 @@ class AlarmKlaxon implements Alarms.AlarmSettings { try { setDataSourceFromResource(context.getResources(), mMediaPlayer, com.android.internal.R.raw.fallbackring); + mMediaPlayer.prepare(); + mMediaPlayer.start(); } catch (Exception ex2) { // At this point we just don't play anything. Log.e("Failed to play fallback ringtone", ex2); } } - /* Now try to play the alert. */ - try { - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mMediaPlayer.setLooping(true); - mMediaPlayer.prepare(); - mMediaPlayer.start(); - } catch (Exception ex) { - Log.e("Error playing alarm: " + mAlert, ex); - } /* Start the vibrator after everything is ok with the media player */ if (mVibrate) { -- cgit v1.2.3 From 3a9b30028bae970b20041c301aff448c0ab1d233 Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Tue, 24 Mar 2009 22:41:39 -0700 Subject: Automated import from //branches/master/...@142357,142357 --- src/com/android/alarmclock/AlarmKlaxon.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index e75eb80dd..0e42daa65 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -121,6 +121,8 @@ class AlarmKlaxon implements Alarms.AlarmSettings { return true; } }); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + mMediaPlayer.setLooping(true); try { TelephonyManager tm = (TelephonyManager) context.getSystemService( @@ -135,6 +137,8 @@ class AlarmKlaxon implements Alarms.AlarmSettings { } else { mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); } + mMediaPlayer.prepare(); + mMediaPlayer.start(); } catch (Exception ex) { Log.v("Using the fallback ringtone"); // The alert may be on the sd card which could be busy right now. @@ -142,20 +146,13 @@ class AlarmKlaxon implements Alarms.AlarmSettings { try { setDataSourceFromResource(context.getResources(), mMediaPlayer, com.android.internal.R.raw.fallbackring); + mMediaPlayer.prepare(); + mMediaPlayer.start(); } catch (Exception ex2) { // At this point we just don't play anything. Log.e("Failed to play fallback ringtone", ex2); } } - /* Now try to play the alert. */ - try { - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mMediaPlayer.setLooping(true); - mMediaPlayer.prepare(); - mMediaPlayer.start(); - } catch (Exception ex) { - Log.e("Error playing alarm: " + mAlert, ex); - } /* Start the vibrator after everything is ok with the media player */ if (mVibrate) { -- cgit v1.2.3 From 5fcbab66db30d2f96c7ae12a75bd1e1652ebe66b Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Wed, 25 Mar 2009 15:44:42 -0700 Subject: Automated import from //branches/master/...@142733,142733 --- src/com/android/alarmclock/AlarmKlaxon.java | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index 0e42daa65..95bb0e2ca 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -121,8 +121,6 @@ class AlarmKlaxon implements Alarms.AlarmSettings { return true; } }); - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mMediaPlayer.setLooping(true); try { TelephonyManager tm = (TelephonyManager) context.getSystemService( @@ -137,17 +135,17 @@ class AlarmKlaxon implements Alarms.AlarmSettings { } else { mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); } - mMediaPlayer.prepare(); - mMediaPlayer.start(); + startAlarm(mMediaPlayer); } catch (Exception ex) { Log.v("Using the fallback ringtone"); // The alert may be on the sd card which could be busy right now. // Use the fallback ringtone. try { + // Must reset the media player to clear the error state. + mMediaPlayer.reset(); setDataSourceFromResource(context.getResources(), mMediaPlayer, com.android.internal.R.raw.fallbackring); - mMediaPlayer.prepare(); - mMediaPlayer.start(); + startAlarm(mMediaPlayer); } catch (Exception ex2) { // At this point we just don't play anything. Log.e("Failed to play fallback ringtone", ex2); @@ -165,6 +163,16 @@ class AlarmKlaxon implements Alarms.AlarmSettings { mPlaying = true; } + // Do the common stuff when starting the alarm. + private void startAlarm(MediaPlayer player) + throws java.io.IOException, IllegalArgumentException, + IllegalStateException { + player.setAudioStreamType(AudioManager.STREAM_ALARM); + player.setLooping(true); + player.prepare(); + player.start(); + } + private void setDataSourceFromResource(Resources resources, MediaPlayer player, int res) throws java.io.IOException { AssetFileDescriptor afd = resources.openRawResourceFd(res); -- cgit v1.2.3 From c2d6bcdb50f63370f3edd97cfc7077e7c1e8e151 Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Wed, 25 Mar 2009 15:47:08 -0700 Subject: Automated import from //branches/donutburger/...@142694,142694 --- src/com/android/alarmclock/AlarmKlaxon.java | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index 0e42daa65..95bb0e2ca 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -121,8 +121,6 @@ class AlarmKlaxon implements Alarms.AlarmSettings { return true; } }); - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mMediaPlayer.setLooping(true); try { TelephonyManager tm = (TelephonyManager) context.getSystemService( @@ -137,17 +135,17 @@ class AlarmKlaxon implements Alarms.AlarmSettings { } else { mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); } - mMediaPlayer.prepare(); - mMediaPlayer.start(); + startAlarm(mMediaPlayer); } catch (Exception ex) { Log.v("Using the fallback ringtone"); // The alert may be on the sd card which could be busy right now. // Use the fallback ringtone. try { + // Must reset the media player to clear the error state. + mMediaPlayer.reset(); setDataSourceFromResource(context.getResources(), mMediaPlayer, com.android.internal.R.raw.fallbackring); - mMediaPlayer.prepare(); - mMediaPlayer.start(); + startAlarm(mMediaPlayer); } catch (Exception ex2) { // At this point we just don't play anything. Log.e("Failed to play fallback ringtone", ex2); @@ -165,6 +163,16 @@ class AlarmKlaxon implements Alarms.AlarmSettings { mPlaying = true; } + // Do the common stuff when starting the alarm. + private void startAlarm(MediaPlayer player) + throws java.io.IOException, IllegalArgumentException, + IllegalStateException { + player.setAudioStreamType(AudioManager.STREAM_ALARM); + player.setLooping(true); + player.prepare(); + player.start(); + } + private void setDataSourceFromResource(Resources resources, MediaPlayer player, int res) throws java.io.IOException { AssetFileDescriptor afd = resources.openRawResourceFd(res); -- cgit v1.2.3 From 5d6f5fa51fa7fd13784a73199161deabec5bc898 Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Mon, 6 Apr 2009 07:07:41 -0700 Subject: AI 144629: Updated UI for alarm labels. Draw the label underneath the repeat days. Use a slightly larger font than the repeat string. Also use bold and italics to distinguish the label. To avoid further clunkiness, do not draw the default label except in the alert dialog. BUG=1716467 Automated import of CL 144629 --- src/com/android/alarmclock/AlarmClock.java | 3 ++- src/com/android/alarmclock/Alarms.java | 14 +++++++------- src/com/android/alarmclock/SetAlarm.java | 3 --- 3 files changed, 9 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index 06ff42c7d..2b542b630 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -161,8 +161,9 @@ public class AlarmClock extends Activity { (TextView) digitalClock.findViewById(R.id.label); if (label != null && label.length() != 0) { labelView.setText(label); + labelView.setVisibility(View.VISIBLE); } else { - labelView.setText(R.string.default_label); + labelView.setVisibility(View.GONE); } // Build context menu diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index 881723493..edc3e4da1 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -601,7 +601,9 @@ public class Alarms { Intent intent = new Intent(ALARM_ALERT_ACTION); if (Log.LOGV) Log.v("** setAlert id " + id + " atTime " + atTimeInMillis); intent.putExtra(ID, id); - intent.putExtra(LABEL, label); + if (label != null) { + intent.putExtra(LABEL, label); + } intent.putExtra(TIME, atTimeInMillis); PendingIntent sender = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); @@ -646,7 +648,9 @@ public class Alarms { SharedPreferences.Editor ed = prefs.edit(); ed.putInt(PREF_SNOOZE_ID, id); ed.putLong(PREF_SNOOZE_TIME, atTimeInMillis); - ed.putString(PREF_SNOOZE_LABEL, label); + if (label != null) { + ed.putString(PREF_SNOOZE_LABEL, label); + } ed.commit(); } @@ -681,12 +685,8 @@ public class Alarms { if (id == -1) return false; long atTimeInMillis = prefs.getLong(PREF_SNOOZE_TIME, -1); if (id == -1) return false; - // Try to get the label from the snooze preference. If null, use the - // default label. + // Try to get the label from the snooze preference. String label = prefs.getString(PREF_SNOOZE_LABEL, null); - if (label == null) { - label = context.getString(R.string.default_label); - } enableAlert(context, id, label, atTimeInMillis); return true; } diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index 807b469df..23c8d65b8 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -208,9 +208,6 @@ public class SetAlarm extends PreferenceActivity Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String label, String alert) { - if (label == null || label.length() == 0) { - label = getString(R.string.default_label); - } mLabel.setText(label); mLabel.setSummary(label); mHour = hour; -- cgit v1.2.3 From f4a8923caf76a5c8febba8943c6f5b33c03a8f0b Mon Sep 17 00:00:00 2001 From: Patrick Scott <> Date: Mon, 13 Apr 2009 09:57:38 -0700 Subject: AI 145870: Add a build property for the default alarm alert. Update the various framework classes to deal with the new property. Also update various documentation that mentions the default ringtones. Use the build property as the default alert when the user has not chosen an alert for an alarm. This is also used if the alarm alert is null when the alarm fires. BUG=1723684 Automated import of CL 145870 --- src/com/android/alarmclock/AlarmKlaxon.java | 15 ++++++++++++++- src/com/android/alarmclock/SetAlarm.java | 12 ++---------- 2 files changed, 16 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index 95bb0e2ca..6c9ffa43e 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnErrorListener; +import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -109,6 +110,18 @@ class AlarmKlaxon implements Alarms.AlarmSettings { if (Log.LOGV) Log.v("AlarmKlaxon.play() " + mAlarmId + " alert " + mAlert); + // Fall back on the default alarm if the database does not have an + // alarm stored. + Uri alert = null; + if (mAlert == null) { + alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); + if (Log.LOGV) { + Log.v("Using default alarm: " + alert.toString()); + } + } else { + alert = Uri.parse(mAlert); + } + // TODO: Reuse mMediaPlayer instead of creating a new one and/or use // RingtoneManager. mMediaPlayer = new MediaPlayer(); @@ -133,7 +146,7 @@ class AlarmKlaxon implements Alarms.AlarmSettings { setDataSourceFromResource(context.getResources(), mMediaPlayer, R.raw.in_call_alarm); } else { - mMediaPlayer.setDataSource(context, Uri.parse(mAlert)); + mMediaPlayer.setDataSource(context, alert); } startAlarm(mMediaPlayer); } catch (Exception ex) { diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index 23c8d65b8..12f9e152e 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -218,7 +218,8 @@ public class SetAlarm extends PreferenceActivity if (alert == null || alert.length() == 0) { if (Log.LOGV) Log.v("****** reportAlarm null or 0-length alert"); - mAlarmPref.mAlert = getDefaultAlarm(); + mAlarmPref.mAlert = + RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); if (mAlarmPref.mAlert == null) { Log.e("****** Default Alarm null"); } @@ -237,15 +238,6 @@ public class SetAlarm extends PreferenceActivity mReportAlarmCalled = true; } - /** - * picks the first alarm available - */ - private Uri getDefaultAlarm() { - RingtoneManager ringtoneManager = new RingtoneManager(this); - ringtoneManager.setType(RingtoneManager.TYPE_ALARM); - return ringtoneManager.getRingtoneUri(0); - } - private void updateTime() { if (Log.LOGV) Log.v("updateTime " + mId); mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, mDaysOfWeek)); -- cgit v1.2.3 From 0dffdefe54299ee01b45574244c92d3a06507b87 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 14 Apr 2009 16:55:10 -0400 Subject: Extend the ListView selection behind the checkbox. Extend the selection behind the checkbox and change the behavior of selecting an alarm item. Long pressing the item now brings up a context menu that can enable or disable the alarm in addition to deleting the alarm. The context menu header now contains the label of the alarm if it exists and it mimics the standard dialog header view. Updated some of the layout code to ensure that the default three Alarms do not show a scrollbar. BUG=1438269 --- src/com/android/alarmclock/AlarmClock.java | 136 ++++++++++++++++++++--------- 1 file changed, 93 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index 2b542b630..c24f20a7b 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -34,7 +34,11 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; import android.widget.CursorAdapter; import android.widget.ListView; import android.widget.TextView; @@ -45,7 +49,7 @@ import java.util.Calendar; /** * AlarmClock application. */ -public class AlarmClock extends Activity { +public class AlarmClock extends Activity implements OnItemClickListener { final static String PREFERENCES = "AlarmClock"; final static String PREF_CLOCK_FACE = "face"; @@ -62,8 +66,6 @@ public class AlarmClock extends Activity { private LayoutInflater mFactory; private ViewGroup mClockLayout; private View mClock = null; - private MenuItem mAddAlarmItem; - private MenuItem mToggleClockItem; private ListView mAlarmsList; private Cursor mCursor; @@ -123,22 +125,6 @@ public class AlarmClock extends Activity { if (Log.LOGV) Log.v("bindView " + cursor.getPosition() + " " + id + " " + hour + ":" + minutes + " " + daysOfWeek.toString(context, true) + " dc " + digitalClock); - digitalClock.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - if (true) { - Intent intent = new Intent(AlarmClock.this, SetAlarm.class); - intent.putExtra(Alarms.ID, id); - startActivity(intent); - } else { - // TESTING: immediately pop alarm - Intent fireAlarm = new Intent(AlarmClock.this, AlarmAlert.class); - fireAlarm.putExtra(Alarms.ID, id); - fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(fireAlarm); - } - } - }); - // set the alarm text final Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR_OF_DAY, hour); @@ -165,34 +151,55 @@ public class AlarmClock extends Activity { } else { labelView.setVisibility(View.GONE); } - - // Build context menu - digitalClock.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { - public void onCreateContextMenu(ContextMenu menu, View view, - ContextMenuInfo menuInfo) { - menu.setHeaderTitle(Alarms.formatTime(AlarmClock.this, c)); - MenuItem deleteAlarmItem = menu.add(0, id, 0, R.string.delete_alarm); - } - }); } }; + private boolean isAlarmEnabled(final Cursor c) { + return c.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1; + } + @Override public boolean onContextItemSelected(final MenuItem item) { - // Confirm that the alarm will be deleted. - new AlertDialog.Builder(this) - .setTitle(getString(R.string.delete_alarm)) - .setMessage(getString(R.string.delete_alarm_confirm)) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface d, int w) { - Alarms.deleteAlarm(AlarmClock.this, - item.getItemId()); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .show(); - return true; + final AdapterContextMenuInfo info = + (AdapterContextMenuInfo) item.getMenuInfo(); + final int id = (int) info.id; + switch (item.getItemId()) { + case R.id.delete_alarm: + // Confirm that the alarm will be deleted. + new AlertDialog.Builder(this) + .setTitle(getString(R.string.delete_alarm)) + .setMessage(getString(R.string.delete_alarm_confirm)) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface d, + int w) { + Alarms.deleteAlarm(AlarmClock.this, id); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + return true; + + case R.id.enable_alarm: + final Cursor c = (Cursor) mAlarmsList.getAdapter() + .getItem(info.position); + boolean enabled = isAlarmEnabled(c); + Alarms.enableAlarm(this, id, !enabled); + if (!enabled) { + final int hour = + c.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX); + final int minutes = + c.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX); + final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek( + c.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); + SetAlarm.popAlarmSetToast(this, hour, minutes, daysOfWeek); + } + return true; + + default: + break; + } + return super.onContextItemSelected(item); } @Override @@ -229,7 +236,8 @@ public class AlarmClock extends Activity { mAlarmsList = (ListView) findViewById(R.id.alarms_list); mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); mAlarmsList.setVerticalScrollBarEnabled(true); - mAlarmsList.setItemsCanFocus(true); + mAlarmsList.setOnItemClickListener(this); + mAlarmsList.setOnCreateContextMenuListener(this); mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); mClockLayout.setOnClickListener(new View.OnClickListener() { @@ -280,6 +288,48 @@ public class AlarmClock extends Activity { return super.onCreateOptionsMenu(menu); } + @Override + public void onCreateContextMenu(ContextMenu menu, View view, + ContextMenuInfo menuInfo) { + // Inflate the menu from xml. + getMenuInflater().inflate(R.menu.context_menu, menu); + + // Use the current item to create a custom view for the header. + final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + final Cursor c = + (Cursor) mAlarmsList.getAdapter().getItem((int) info.position); + final int hour = c.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX); + final int minutes = c.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX); + final String label = + c.getString(Alarms.AlarmColumns.ALARM_MESSAGE_INDEX); + + // Construct the Calendar to compute the time. + final Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, hour); + cal.set(Calendar.MINUTE, minutes); + final String time = Alarms.formatTime(this, cal); + + // Inflate the custom view and set each TextView's text. + final View v = mFactory.inflate(R.layout.context_menu_header, null); + TextView textView = (TextView) v.findViewById(R.id.header_time); + textView.setText(time); + textView = (TextView) v.findViewById(R.id.header_label); + textView.setText(label); + + // Set the custom view on the menu. + menu.setHeaderView(v); + // Change the text to "disable" if the alarm is already enabled. + if (isAlarmEnabled(c)) { + menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm); + } + } + + public void onItemClick(AdapterView parent, View v, int pos, long id) { + Intent intent = new Intent(this, SetAlarm.class); + intent.putExtra(Alarms.ID, (int) id); + startActivity(intent); + } + /** * Only allow user to add a new alarm if there are fewer than * MAX_ALARM_COUNT -- cgit v1.2.3 From eb142ac59aa8039c409b5fe4f50a130f16960d41 Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Wed, 15 Apr 2009 17:13:18 -0700 Subject: Don't maintain separate translations in Alarm Clock for days of the week. Use DateFormatSymbols instead to get the strings for the names of the days the week and for "AM" and "PM". --- src/com/android/alarmclock/AlarmClock.java | 22 +++++++++++++++++++++ src/com/android/alarmclock/Alarms.java | 25 +++++++++++++++++++----- src/com/android/alarmclock/RepeatPreference.java | 16 +++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index 06ff42c7d..24f41d145 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -41,6 +41,7 @@ import android.widget.TextView; import android.widget.CheckBox; import java.util.Calendar; +import java.text.DateFormatSymbols; /** * AlarmClock application. @@ -67,6 +68,8 @@ public class AlarmClock extends Activity { private ListView mAlarmsList; private Cursor mCursor; + private String mAm, mPm; + /** * Which clock face to show */ @@ -90,6 +93,10 @@ public class AlarmClock extends Activity { public View newView(Context context, Cursor cursor, ViewGroup parent) { View ret = mFactory.inflate(R.layout.alarm_time, parent, false); + + ((TextView) ret.findViewById(R.id.am)).setText(mAm); + ((TextView) ret.findViewById(R.id.pm)).setText(mPm); + DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); digitalClock.setLive(false); if (Log.LOGV) Log.v("newView " + cursor.getPosition()); @@ -198,6 +205,10 @@ public class AlarmClock extends Activity { protected void onCreate(Bundle icicle) { super.onCreate(icicle); + String[] ampm = new DateFormatSymbols().getAmPmStrings(); + mAm = ampm[0]; + mPm = ampm[1]; + // sanity check -- no database, no clock if (getContentResolver() == null) { new AlertDialog.Builder(this) @@ -268,6 +279,17 @@ public class AlarmClock extends Activity { mClockLayout.removeView(mClock); } mClock = mFactory.inflate(CLOCKS[mFace], null); + + TextView am = (TextView) mClock.findViewById(R.id.am); + TextView pm = (TextView) mClock.findViewById(R.id.pm); + + if (am != null) { + am.setText(mAm); + } + if (pm != null) { + pm.setText(mPm); + } + mClockLayout.addView(mClock, 0); } diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index 881723493..e34b1b56c 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -33,6 +33,7 @@ import android.provider.Settings; import android.text.format.DateFormat; import java.util.Calendar; +import java.text.DateFormatSymbols; /** * The Alarms provider supplies info about Alarm Clock settings @@ -54,6 +55,20 @@ public class Alarms { private final static String M12 = "h:mm aa"; private final static String M24 = "k:mm"; + /** + * Mapping from days in this application (where Monday is 0) to + * days in DateFormatSymbols (where Monday is 2). + */ + private static int[] DAY_MAP = new int[] { + Calendar.MONDAY, + Calendar.TUESDAY, + Calendar.WEDNESDAY, + Calendar.THURSDAY, + Calendar.FRIDAY, + Calendar.SATURDAY, + Calendar.SUNDAY, + }; + static class DaysOfWeek { int mDays; @@ -99,15 +114,15 @@ public class Alarms { } /* short or long form? */ - CharSequence[] strings = - context.getResources().getTextArray( - (dayCount > 1) ? R.array.days_of_week_short : - R.array.days_of_week); + DateFormatSymbols dfs = new DateFormatSymbols(); + String[] dayList = (dayCount > 1) ? + dfs.getShortWeekdays() : + dfs.getWeekdays(); /* selected days */ for (int i = 0; i < 7; i++) { if ((mDays & (1 << i)) != 0) { - ret.append(strings[i]); + ret.append(dayList[DAY_MAP[i]]); dayCount -= 1; if (dayCount > 0) ret.append( context.getText(R.string.day_concat)); diff --git a/src/com/android/alarmclock/RepeatPreference.java b/src/com/android/alarmclock/RepeatPreference.java index 8313858a4..ba33faa27 100644 --- a/src/com/android/alarmclock/RepeatPreference.java +++ b/src/com/android/alarmclock/RepeatPreference.java @@ -22,6 +22,9 @@ import android.content.DialogInterface; import android.preference.ListPreference; import android.util.AttributeSet; +import java.text.DateFormatSymbols; +import java.util.Calendar; + public class RepeatPreference extends ListPreference { private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); @@ -37,6 +40,19 @@ public class RepeatPreference extends ListPreference { public RepeatPreference(Context context, AttributeSet attrs) { super(context, attrs); + + String[] weekdays = new DateFormatSymbols().getWeekdays(); + String[] values = new String[] { + weekdays[Calendar.MONDAY], + weekdays[Calendar.TUESDAY], + weekdays[Calendar.WEDNESDAY], + weekdays[Calendar.THURSDAY], + weekdays[Calendar.FRIDAY], + weekdays[Calendar.SATURDAY], + weekdays[Calendar.SUNDAY], + }; + setEntries(values); + setEntryValues(values); } void setOnRepeatChangedObserver(OnRepeatChangedObserver onRepeatChangedObserver) { -- cgit v1.2.3 From abbdb5687b2f4505b16d3f8fad3efbab3ea1cf90 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 28 Apr 2009 16:37:04 -0400 Subject: Add a setting to change the snooze duration. Add a ListPreference to the SettingsActivity that has entries for 5, 10, 15, 20, 25, and 30 minutes. Use the entry value as the summary so the user knows the current setting. Use the preference value or the default of 10 in AlarmAlert. --- src/com/android/alarmclock/AlarmAlert.java | 12 +++++-- src/com/android/alarmclock/SettingsActivity.java | 40 +++++++++++++++++------- 2 files changed, 37 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index a89a88437..09e2c40e4 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -24,6 +24,7 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.Bundle; +import android.preference.PreferenceManager; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -41,7 +42,7 @@ import java.util.Calendar; */ public class AlarmAlert extends Activity { - private static final int SNOOZE_MINUTES = 10; + private static final String DEFAULT_SNOOZE = "10"; private static final int UNKNOWN = 0; private static final int SNOOZE = 1; private static final int DISMISS = 2; @@ -188,8 +189,13 @@ public class AlarmAlert extends Activity { } // If the next alarm is set for sooner than the snooze interval, don't // snooze. Instead, toast the user that the snooze will not be set. + final String snooze = + PreferenceManager.getDefaultSharedPreferences(this) + .getString("snooze_duration", DEFAULT_SNOOZE); + int snoozeMinutes = Integer.parseInt(snooze); + final long snoozeTime = System.currentTimeMillis() - + (1000 * 60 * SNOOZE_MINUTES); + + (1000 * 60 * snoozeMinutes); final long nextAlarm = Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); String displayTime = null; @@ -204,7 +210,7 @@ public class AlarmAlert extends Activity { mLabel); Alarms.setNextAlert(AlarmAlert.this); displayTime = getString(R.string.alarm_alert_snooze_set, - SNOOZE_MINUTES); + snoozeMinutes); mState = SNOOZE; } // Intentionally log the snooze time for debugging. diff --git a/src/com/android/alarmclock/SettingsActivity.java b/src/com/android/alarmclock/SettingsActivity.java index 44c4045ff..59bf04022 100644 --- a/src/com/android/alarmclock/SettingsActivity.java +++ b/src/com/android/alarmclock/SettingsActivity.java @@ -19,6 +19,7 @@ package com.android.alarmclock; import android.media.AudioManager; import android.os.Bundle; import android.preference.CheckBoxPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; @@ -27,23 +28,31 @@ import android.provider.Settings; /** * Settings for the Alarm Clock. */ -public class SettingsActivity extends PreferenceActivity { +public class SettingsActivity extends PreferenceActivity + implements Preference.OnPreferenceChangeListener { private static final int ALARM_STREAM_TYPE_BIT = 1 << AudioManager.STREAM_ALARM; - + private static final String KEY_ALARM_IN_SILENT_MODE = "alarm_in_silent_mode"; + private static final String KEY_ALARM_SNOOZE = + "snooze_duration"; private CheckBoxPreference mAlarmInSilentModePref; - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - + addPreferencesFromResource(R.xml.settings); - + mAlarmInSilentModePref = (CheckBoxPreference) findPreference(KEY_ALARM_IN_SILENT_MODE); + + final ListPreference snooze = + (ListPreference) findPreference(KEY_ALARM_SNOOZE); + snooze.setSummary(snooze.getEntry()); + snooze.setOnPreferenceChangeListener(this); } @Override @@ -55,34 +64,41 @@ public class SettingsActivity extends PreferenceActivity { @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - + if (preference == mAlarmInSilentModePref) { - + int ringerModeStreamTypes = Settings.System.getInt( getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - + if (mAlarmInSilentModePref.isChecked()) { ringerModeStreamTypes &= ~ALARM_STREAM_TYPE_BIT; } else { ringerModeStreamTypes |= ALARM_STREAM_TYPE_BIT; } - + Settings.System.putInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, ringerModeStreamTypes); - + return true; } - + return super.onPreferenceTreeClick(preferenceScreen, preference); } + public boolean onPreferenceChange(Preference pref, Object newValue) { + ListPreference listPref = (ListPreference) pref; + int idx = listPref.findIndexOfValue((String) newValue); + listPref.setSummary(listPref.getEntries()[idx]); + return true; + } + private void refresh() { int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); mAlarmInSilentModePref.setChecked( (silentModeStreams & ALARM_STREAM_TYPE_BIT) == 0); } - + } -- cgit v1.2.3 From b83db52b375a9a28347cacff3a8e8b1ebabe3b46 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Thu, 30 Apr 2009 08:23:05 -0400 Subject: Cleanup the preference code for editing an alarm. Add a save and cancel bar at the bottom similar to the contacts application. Rather than updating the alarm in the database for every change in the settings, the user must hit 'save' to save the changes. Update AlarmPreference and RepeatPreference to manage their own summary instead of having callbacks. When saving the alarm, retrieve all the values from the preference widgets. Make each preference non-persistent to avoid saving them to the shared preferences. In order to get the save/cancel buttons flush on the bottom, I have to undo what the PreferenceActivity does in onCreate. I then build my own LinearLayout to hold the ListView and the two buttons. --- src/com/android/alarmclock/AlarmPreference.java | 41 ++-- src/com/android/alarmclock/RepeatPreference.java | 45 ++--- src/com/android/alarmclock/SetAlarm.java | 241 +++++++++-------------- 3 files changed, 140 insertions(+), 187 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmPreference.java b/src/com/android/alarmclock/AlarmPreference.java index 0fd4f89dd..f6a7bb096 100644 --- a/src/com/android/alarmclock/AlarmPreference.java +++ b/src/com/android/alarmclock/AlarmPreference.java @@ -17,38 +17,47 @@ package com.android.alarmclock; import android.content.Context; +import android.media.Ringtone; +import android.media.RingtoneManager; import android.net.Uri; import android.preference.RingtonePreference; import android.util.AttributeSet; +/** + * The RingtonePreference does not have a way to get/set the current ringtone so + * we override onSaveRingtone and onRestoreRingtone to get the same behavior. + */ public class AlarmPreference extends RingtonePreference { - public Uri mAlert; - private IRingtoneChangedListener mRingtoneChangedListener; - - public interface IRingtoneChangedListener { - public void onRingtoneChanged(Uri ringtoneUri); - }; + private Uri mAlert; public AlarmPreference(Context context, AttributeSet attrs) { super(context, attrs); } - public void setRingtoneChangedListener(IRingtoneChangedListener listener) { - mRingtoneChangedListener = listener; - } - @Override protected void onSaveRingtone(Uri ringtoneUri) { - if (ringtoneUri != null) { - mAlert = ringtoneUri; - if (mRingtoneChangedListener != null) { - mRingtoneChangedListener.onRingtoneChanged(ringtoneUri); - } - } + setAlert(ringtoneUri); } @Override protected Uri onRestoreRingtone() { return mAlert; } + + public void setAlert(Uri alert) { + if (alert != null) { + mAlert = alert; + final Ringtone r = RingtoneManager.getRingtone(getContext(), alert); + if (r != null) { + setSummary(r.getTitle(getContext())); + } + } + } + + public String getAlertString() { + if (mAlert != null) { + return mAlert.toString(); + } + return null; + } } diff --git a/src/com/android/alarmclock/RepeatPreference.java b/src/com/android/alarmclock/RepeatPreference.java index ba33faa27..7111ec6d2 100644 --- a/src/com/android/alarmclock/RepeatPreference.java +++ b/src/com/android/alarmclock/RepeatPreference.java @@ -27,16 +27,11 @@ import java.util.Calendar; public class RepeatPreference extends ListPreference { + // Initial value that can be set with the values saved in the database. private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); - private OnRepeatChangedObserver mOnRepeatChangedObserver; - - public interface OnRepeatChangedObserver { - /** RepeatPrefrence calls this to get initial state */ - public Alarms.DaysOfWeek getDaysOfWeek(); - - /** Called when this preference has changed */ - public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek); - } + // New value that will be set if a positive result comes back from the + // dialog. + private Alarms.DaysOfWeek mNewDaysOfWeek = new Alarms.DaysOfWeek(); public RepeatPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -55,17 +50,11 @@ public class RepeatPreference extends ListPreference { setEntryValues(values); } - void setOnRepeatChangedObserver(OnRepeatChangedObserver onRepeatChangedObserver) { - mOnRepeatChangedObserver = onRepeatChangedObserver; - } - @Override protected void onDialogClosed(boolean positiveResult) { if (positiveResult) { - mOnRepeatChangedObserver.onRepeatChanged(mDaysOfWeek); - } else { - /* no change -- reset to initial state */ - mDaysOfWeek.set(mOnRepeatChangedObserver.getDaysOfWeek()); + mDaysOfWeek.set(mNewDaysOfWeek); + setSummary(mDaysOfWeek.toString(getContext(), true)); } } @@ -74,19 +63,23 @@ public class RepeatPreference extends ListPreference { CharSequence[] entries = getEntries(); CharSequence[] entryValues = getEntryValues(); - if (entries == null || entryValues == null) { - throw new IllegalStateException( - "RepeatPreference requires an entries array and an entryValues array."); - } - - mDaysOfWeek.set(mOnRepeatChangedObserver.getDaysOfWeek()); - builder.setMultiChoiceItems( entries, mDaysOfWeek.getBooleanArray(), new DialogInterface.OnMultiChoiceClickListener() { - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - mDaysOfWeek.set(which, isChecked); + public void onClick(DialogInterface dialog, int which, + boolean isChecked) { + mNewDaysOfWeek.set(which, isChecked); } }); } + + public void setDaysOfWeek(Alarms.DaysOfWeek dow) { + mDaysOfWeek.set(dow); + mNewDaysOfWeek.set(dow); + setSummary(dow.toString(getContext(), true)); + } + + public Alarms.DaysOfWeek getDaysOfWeek() { + return mDaysOfWeek; + } } diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index 12f9e152e..5df4cf5c8 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -16,25 +16,27 @@ package com.android.alarmclock; -import android.app.Activity; -import android.app.Dialog; import android.app.TimePickerDialog; import android.content.Context; import android.content.Intent; -import android.database.ContentObserver; -import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.text.format.DateFormat; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListView; import android.widget.TimePicker; import android.widget.Toast; @@ -45,52 +47,19 @@ public class SetAlarm extends PreferenceActivity implements Alarms.AlarmSettings, TimePickerDialog.OnTimeSetListener { private EditTextPreference mLabel; - private CheckBoxPreference mAlarmOnPref; private Preference mTimePref; private AlarmPreference mAlarmPref; private CheckBoxPreference mVibratePref; private RepeatPreference mRepeatPref; - private ContentObserver mAlarmsChangeObserver; private MenuItem mDeleteAlarmItem; private MenuItem mTestAlarmItem; private int mId; private int mHour; private int mMinutes; - private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); private boolean mReportAlarmCalled; - private static final int DIALOG_TIMEPICKER = 0; - - private class RingtoneChangedListener implements AlarmPreference.IRingtoneChangedListener { - public void onRingtoneChanged(Uri ringtoneUri) { - saveAlarm(false); - } - } - - private class OnRepeatChangedObserver implements RepeatPreference.OnRepeatChangedObserver { - public void onRepeatChanged(Alarms.DaysOfWeek daysOfWeek) { - if (!mDaysOfWeek.equals(daysOfWeek)) { - mDaysOfWeek.set(daysOfWeek); - saveAlarm(true); - } - } - public Alarms.DaysOfWeek getDaysOfWeek() { - return mDaysOfWeek; - } - } - - private class AlarmsChangeObserver extends ContentObserver { - public AlarmsChangeObserver() { - super(new Handler()); - } - @Override - public void onChange(boolean selfChange) { - Alarms.getAlarm(getContentResolver(), SetAlarm.this, mId); - } - } - /** * Set an alarm. Requires an Alarms.ID to be passed in as an * extra @@ -100,17 +69,18 @@ public class SetAlarm extends PreferenceActivity super.onCreate(icicle); addPreferencesFromResource(R.xml.alarm_prefs); + + // Get each preference so we can retrieve the value later. mLabel = (EditTextPreference) findPreference("label"); mLabel.setOnPreferenceChangeListener( new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference p, Object newValue) { + // Set the summary based on the new label. p.setSummary((String) newValue); - saveAlarm(false, (String) newValue); return true; } }); - mAlarmOnPref = (CheckBoxPreference)findPreference("on"); mTimePref = findPreference("time"); mAlarmPref = (AlarmPreference) findPreference("alarm"); mVibratePref = (CheckBoxPreference) findPreference("vibrate"); @@ -118,7 +88,9 @@ public class SetAlarm extends PreferenceActivity Intent i = getIntent(); mId = i.getIntExtra(Alarms.ID, -1); - if (Log.LOGV) Log.v("In SetAlarm, alarm id = " + mId); + if (Log.LOGV) { + Log.v("In SetAlarm, alarm id = " + mId); + } mReportAlarmCalled = false; /* load alarm details from database */ @@ -131,62 +103,60 @@ public class SetAlarm extends PreferenceActivity finish(); } - mAlarmsChangeObserver = new AlarmsChangeObserver(); - getContentResolver().registerContentObserver( - Alarms.AlarmColumns.CONTENT_URI, true, mAlarmsChangeObserver); - - mAlarmPref.setRingtoneChangedListener(new RingtoneChangedListener()); - mRepeatPref.setOnRepeatChangedObserver(new OnRepeatChangedObserver()); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - getContentResolver().unregisterContentObserver(mAlarmsChangeObserver); - } - - @Override - protected Dialog onCreateDialog(int id) { - Dialog d; - - switch (id) { - case DIALOG_TIMEPICKER: - d = new TimePickerDialog( - SetAlarm.this, - this, - 0, - 0, - DateFormat.is24HourFormat(SetAlarm.this)); - d.setTitle(getResources().getString(R.string.time)); - break; - default: - d = null; - } - - return d; + // We have to do this to get the save/cancel buttons to highlight on + // their own. + getListView().setItemsCanFocus(true); + + // Grab the content view so we can modify it. + FrameLayout content = (FrameLayout) getWindow().getDecorView() + .findViewById(com.android.internal.R.id.content); + + // Get the main ListView and remove it from the content view. + ListView lv = getListView(); + content.removeView(lv); + + // Create the new LinearLayout that will become the content view and + // make it vertical. + LinearLayout ll = new LinearLayout(this); + ll.setOrientation(LinearLayout.VERTICAL); + + // Have the ListView expand to fill the screen minus the save/cancel + // buttons. + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LayoutParams.FILL_PARENT, + LayoutParams.WRAP_CONTENT); + lp.weight = 1; + ll.addView(lv, lp); + + // Inflate the buttons onto the LinearLayout. + View v = LayoutInflater.from(this).inflate( + R.layout.save_cancel_alarm, ll); + + // Attach actions to each button. + Button b = (Button) v.findViewById(R.id.alarm_save); + b.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + saveAlarm(); + finish(); + } + }); + b = (Button) v.findViewById(R.id.alarm_cancel); + b.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + finish(); + } + }); + + // Replace the old content view with our new one. + setContentView(ll); } @Override - protected void onPrepareDialog(int id, Dialog dialog) { - super.onPrepareDialog(id, dialog); - - switch (id) { - case DIALOG_TIMEPICKER: - TimePickerDialog timePicker = (TimePickerDialog)dialog; - timePicker.updateTime(mHour, mMinutes); - break; - } - } - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { if (preference == mTimePref) { - showDialog(DIALOG_TIMEPICKER); - } else if (preference == mAlarmOnPref) { - saveAlarm(true); - } else if (preference == mVibratePref) { - saveAlarm(false); + new TimePickerDialog(this, this, mHour, mMinutes, + DateFormat.is24HourFormat(this)).show(); } return super.onPreferenceTreeClick(preferenceScreen, preference); @@ -195,8 +165,7 @@ public class SetAlarm extends PreferenceActivity public void onTimeSet(TimePicker view, int hourOfDay, int minute) { mHour = hourOfDay; mMinutes = minute; - mAlarmOnPref.setChecked(true); - saveAlarm(true); + updateTime(); } /** @@ -212,65 +181,48 @@ public class SetAlarm extends PreferenceActivity mLabel.setSummary(label); mHour = hour; mMinutes = minutes; - mAlarmOnPref.setChecked(enabled); - mDaysOfWeek.set(daysOfWeek); + mRepeatPref.setDaysOfWeek(daysOfWeek); mVibratePref.setChecked(vibrate); - if (alert == null || alert.length() == 0) { - if (Log.LOGV) Log.v("****** reportAlarm null or 0-length alert"); - mAlarmPref.mAlert = - RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); - if (mAlarmPref.mAlert == null) { - Log.e("****** Default Alarm null"); - } - } else { - mAlarmPref.mAlert = Uri.parse(alert); - if (mAlarmPref.mAlert == null) { - Log.e("****** Parsed null alarm. URI: " + alert); - } + Uri alertUri = null; + if (alert != null && alert.length() != 0) { + alertUri = Uri.parse(alert); + } + + // If the database alert is null or it failed to parse, use the default + // alert. + if (alertUri == null) { + alertUri = RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_ALARM); + } + + if (Log.LOGV) { + Log.v("reportAlarm alert: " + alert + " uri: " + alertUri); } - if (Log.LOGV) Log.v("****** reportAlarm uri " + alert + " alert " + - mAlarmPref.mAlert); + + // Give the alert uri to the preference. + mAlarmPref.setAlert(alertUri); + updateTime(); - updateRepeat(); - updateAlarm(mAlarmPref.mAlert); mReportAlarmCalled = true; } private void updateTime() { - if (Log.LOGV) Log.v("updateTime " + mId); - mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, mDaysOfWeek)); - } - - private void updateAlarm(Uri ringtoneUri) { - if (Log.LOGV) Log.v("updateAlarm " + mId); - Ringtone ringtone = RingtoneManager.getRingtone(SetAlarm.this, ringtoneUri); - if (ringtone != null) { - mAlarmPref.setSummary(ringtone.getTitle(SetAlarm.this)); + if (Log.LOGV) { + Log.v("updateTime " + mId); } + mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, + mRepeatPref.getDaysOfWeek())); } - private void updateRepeat() { - if (Log.LOGV) Log.v("updateRepeat " + mId); - mRepeatPref.setSummary(mDaysOfWeek.toString(this, true)); - } - - private void saveAlarm(boolean popToast) { - saveAlarm(popToast, mLabel.getText()); - } + private void saveAlarm() { + final String alert = mAlarmPref.getAlertString(); + Alarms.setAlarm(this, mId, true, mHour, mMinutes, + mRepeatPref.getDaysOfWeek(), mVibratePref.isChecked(), + mLabel.getText(), alert); - /** - * This version of saveAlarm uses the passed in label since mLabel may - * contain the old value (i.e. during the preference value change). - */ - private void saveAlarm(boolean popToast, String label) { - if (mReportAlarmCalled && mAlarmPref.mAlert != null) { - String alertString = mAlarmPref.mAlert.toString(); - saveAlarm(this, mId, mAlarmOnPref.isChecked(), mHour, mMinutes, - mDaysOfWeek, mVibratePref.isChecked(), label, alertString, - popToast); - } + popAlarmSetToast(this, mHour, mMinutes, mRepeatPref.getDaysOfWeek()); } /** @@ -368,7 +320,6 @@ public class SetAlarm extends PreferenceActivity mTestAlarmItem = menu.add(0, 0, 0, "test alarm"); } - return true; } @@ -403,10 +354,10 @@ public class SetAlarm extends PreferenceActivity int nowMinute = c.get(java.util.Calendar.MINUTE); int minutes = (nowMinute + 1) % 60; - int hour = nowHour + (nowMinute == 0? 1 : 0); + int hour = nowHour + (nowMinute == 0 ? 1 : 0); - saveAlarm(this, mId, true, hour, minutes, mDaysOfWeek, true, - mLabel.getText(), mAlarmPref.mAlert.toString(), true); + saveAlarm(this, mId, true, hour, minutes, mRepeatPref.getDaysOfWeek(), + true, mLabel.getText(), mAlarmPref.getAlertString(), true); } } -- cgit v1.2.3 From c892815bc694522cf760a826349ab66f67ee6d05 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Thu, 14 May 2009 11:12:54 -0400 Subject: Allow the alert to be silent. Turn on the silent item in the ringtone picker. Detect the null alert and use a special "silent" string as the alert to detect a silent alert. Update the summary of the alert to show Silent as the ringtone. Change the SetAlarm manifest to get config changes for orientation and keyboard. This prevents the settings from resetting when changing the orientation. --- src/com/android/alarmclock/AlarmKlaxon.java | 83 ++++++++++++++----------- src/com/android/alarmclock/AlarmPreference.java | 6 +- src/com/android/alarmclock/Alarms.java | 1 + src/com/android/alarmclock/SetAlarm.java | 28 +++++---- 4 files changed, 67 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index 6c9ffa43e..4cf306bcf 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -113,55 +113,62 @@ class AlarmKlaxon implements Alarms.AlarmSettings { // Fall back on the default alarm if the database does not have an // alarm stored. Uri alert = null; + boolean silent = false; if (mAlert == null) { alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); if (Log.LOGV) { Log.v("Using default alarm: " + alert.toString()); } + } else if (Alarms.ALARM_ALERT_SILENT.equals(mAlert)) { + silent = true; } else { alert = Uri.parse(mAlert); } - // TODO: Reuse mMediaPlayer instead of creating a new one and/or use - // RingtoneManager. - mMediaPlayer = new MediaPlayer(); - mMediaPlayer.setOnErrorListener(new OnErrorListener() { - public boolean onError(MediaPlayer mp, int what, int extra) { - Log.e("Error occurred while playing audio."); - mp.stop(); - mp.release(); - mMediaPlayer = null; - return true; - } - }); - - try { - TelephonyManager tm = (TelephonyManager) context.getSystemService( - Context.TELEPHONY_SERVICE); - // Check if we are in a call. If we are, use the in-call alarm - // resource at a low volume to not disrupt the call. - if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) { - Log.v("Using the in-call alarm"); - mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); - setDataSourceFromResource(context.getResources(), - mMediaPlayer, R.raw.in_call_alarm); - } else { - mMediaPlayer.setDataSource(context, alert); - } - startAlarm(mMediaPlayer); - } catch (Exception ex) { - Log.v("Using the fallback ringtone"); - // The alert may be on the sd card which could be busy right now. - // Use the fallback ringtone. + if (!silent) { + // TODO: Reuse mMediaPlayer instead of creating a new one and/or use + // RingtoneManager. + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnErrorListener(new OnErrorListener() { + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e("Error occurred while playing audio."); + mp.stop(); + mp.release(); + mMediaPlayer = null; + return true; + } + }); + try { - // Must reset the media player to clear the error state. - mMediaPlayer.reset(); - setDataSourceFromResource(context.getResources(), mMediaPlayer, - com.android.internal.R.raw.fallbackring); + TelephonyManager tm = + (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + // Check if we are in a call. If we are, use the in-call alarm + // resource at a low volume to not disrupt the call. + if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) { + Log.v("Using the in-call alarm"); + mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); + setDataSourceFromResource(context.getResources(), + mMediaPlayer, R.raw.in_call_alarm); + } else { + mMediaPlayer.setDataSource(context, alert); + } startAlarm(mMediaPlayer); - } catch (Exception ex2) { - // At this point we just don't play anything. - Log.e("Failed to play fallback ringtone", ex2); + } catch (Exception ex) { + Log.v("Using the fallback ringtone"); + // The alert may be on the sd card which could be busy right + // now. Use the fallback ringtone. + try { + // Must reset the media player to clear the error state. + mMediaPlayer.reset(); + setDataSourceFromResource(context.getResources(), + mMediaPlayer, + com.android.internal.R.raw.fallbackring); + startAlarm(mMediaPlayer); + } catch (Exception ex2) { + // At this point we just don't play anything. + Log.e("Failed to play fallback ringtone", ex2); + } } } diff --git a/src/com/android/alarmclock/AlarmPreference.java b/src/com/android/alarmclock/AlarmPreference.java index f6a7bb096..cb0e3f5cd 100644 --- a/src/com/android/alarmclock/AlarmPreference.java +++ b/src/com/android/alarmclock/AlarmPreference.java @@ -45,12 +45,14 @@ public class AlarmPreference extends RingtonePreference { } public void setAlert(Uri alert) { + mAlert = alert; if (alert != null) { - mAlert = alert; final Ringtone r = RingtoneManager.getRingtone(getContext(), alert); if (r != null) { setSummary(r.getTitle(getContext())); } + } else { + setSummary(R.string.silent_alarm_summary); } } @@ -58,6 +60,6 @@ public class AlarmPreference extends RingtonePreference { if (mAlert != null) { return mAlert.toString(); } - return null; + return Alarms.ALARM_ALERT_SILENT; } } diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index 773f87476..40edf0b84 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -44,6 +44,7 @@ public class Alarms { public final static String ID = "alarm_id"; public final static String TIME = "alarm_time"; public final static String LABEL = "alarm_label"; + public final static String ALARM_ALERT_SILENT = "silent"; final static String PREF_SNOOZE_ID = "snooze_id"; final static String PREF_SNOOZE_TIME = "snooze_time"; diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index 5df4cf5c8..bf783fb4b 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -185,19 +185,25 @@ public class SetAlarm extends PreferenceActivity mVibratePref.setChecked(vibrate); Uri alertUri = null; - if (alert != null && alert.length() != 0) { - alertUri = Uri.parse(alert); - } + if (Alarms.ALARM_ALERT_SILENT.equals(alert)) { + if (Log.LOGV) { + Log.v("reportAlarm: silent alert"); + } + } else { + if (alert != null && alert.length() != 0) { + alertUri = Uri.parse(alert); + } - // If the database alert is null or it failed to parse, use the default - // alert. - if (alertUri == null) { - alertUri = RingtoneManager.getDefaultUri( - RingtoneManager.TYPE_ALARM); - } + // If the database alert is null or it failed to parse, use the + // default alert. + if (alertUri == null) { + alertUri = RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_ALARM); + } - if (Log.LOGV) { - Log.v("reportAlarm alert: " + alert + " uri: " + alertUri); + if (Log.LOGV) { + Log.v("reportAlarm alert: " + alert + " uri: " + alertUri); + } } // Give the alert uri to the preference. -- cgit v1.2.3 From aef7a7aafaacfa4237c51e5359b0109e8689a643 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Fri, 15 May 2009 10:11:23 -0400 Subject: Dismiss the alarm when hitting the camera button. --- src/com/android/alarmclock/AlarmAlert.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 09e2c40e4..9fac9ccb9 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -301,6 +301,8 @@ public class AlarmAlert extends Activity { // Volume keys dismiss the alarm case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_CAMERA: + case KeyEvent.KEYCODE_FOCUS: dismiss = true; // All other keys will snooze the alarm default: -- cgit v1.2.3 From 01dee6eedd37dd50961584a7c717c85f00131401 Mon Sep 17 00:00:00 2001 From: Suchi Amalapurapu Date: Thu, 14 May 2009 18:11:28 -0700 Subject: use the new window manager flag FLAG_SHOW_WHEN_LOCKED to hide/show the keyguard window through the window manager rather than enabling or disabling the keyguard --- src/com/android/alarmclock/AlarmAlert.java | 37 ++++++++---------------------- 1 file changed, 9 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index a89a88437..6db9aad64 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -17,17 +17,15 @@ package com.android.alarmclock; import android.app.Activity; -import android.app.KeyguardManager; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; -import android.graphics.PixelFormat; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater; +import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.Toast; @@ -46,9 +44,6 @@ public class AlarmAlert extends Activity { private static final int SNOOZE = 1; private static final int DISMISS = 2; private static final int KILLED = 3; - - private KeyguardManager mKeyguardManager; - private KeyguardManager.KeyguardLock mKeyguardLock; private Button mSnoozeButton; private int mState = UNKNOWN; @@ -63,7 +58,7 @@ public class AlarmAlert extends Activity { // Maintain a lock during the playback of the alarm. This lock may have // already been acquired in AlarmReceiver. If the process was killed, // the global wake lock is gone. Acquire again just to be sure. - AlarmAlertWakeLock.acquire(this); + // AlarmAlertWakeLock.acquire(this); /* FIXME Intentionally verbose: always log this until we've fully debugged the app failing to start up */ @@ -80,10 +75,12 @@ public class AlarmAlert extends Activity { // to work correctly, you should create and show your own custom window. lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; lp.token = null; - getWindow().setAttributes(lp); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - - mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + Window win = getWindow(); + win.setAttributes(lp); + // TODO Make the activity full screen for FLAG_SHOW_WHEN_LOCKED to bypass + // the key guard. + win.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); Intent i = getIntent(); mAlarmId = i.getIntExtra(Alarms.ID, -1); @@ -235,7 +232,6 @@ public class AlarmAlert extends Activity { if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); mState = UNKNOWN; mSnoozeButton.setEnabled(true); - disableKeyguard(); mAlarmId = intent.getIntExtra(Alarms.ID, -1); // Play the new alarm sound. @@ -255,7 +251,7 @@ public class AlarmAlert extends Activity { protected void onResume() { super.onResume(); if (Log.LOGV) Log.v("AlarmAlert.onResume()"); - disableKeyguard(); + AlarmAlertWakeLock.acquire(this); } @Override @@ -314,25 +310,10 @@ public class AlarmAlert extends Activity { return super.dispatchKeyEvent(event); } - private synchronized void enableKeyguard() { - if (mKeyguardLock != null) { - mKeyguardLock.reenableKeyguard(); - mKeyguardLock = null; - } - } - - private synchronized void disableKeyguard() { - if (mKeyguardLock == null) { - mKeyguardLock = mKeyguardManager.newKeyguardLock(Log.LOGTAG); - mKeyguardLock.disableKeyguard(); - } - } - /** * release wake and keyguard locks */ private synchronized void releaseLocks() { AlarmAlertWakeLock.release(); - enableKeyguard(); } } -- cgit v1.2.3 From 3f77eebe6d9f277a8035c8c14b97c69023245b55 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 19 May 2009 19:20:18 -0400 Subject: Acquire the lock in onCreate again. Remove releaseLocks since it only releases the wake lock. --- src/com/android/alarmclock/AlarmAlert.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 5dd5bead5..3677ce7a1 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -59,7 +59,7 @@ public class AlarmAlert extends Activity { // Maintain a lock during the playback of the alarm. This lock may have // already been acquired in AlarmReceiver. If the process was killed, // the global wake lock is gone. Acquire again just to be sure. - // AlarmAlertWakeLock.acquire(this); + AlarmAlertWakeLock.acquire(this); /* FIXME Intentionally verbose: always log this until we've fully debugged the app failing to start up */ @@ -215,7 +215,7 @@ public class AlarmAlert extends Activity { // Display the snooze minutes in a toast. Toast.makeText(AlarmAlert.this, displayTime, Toast.LENGTH_LONG).show(); mKlaxon.stop(this, mState == SNOOZE); - releaseLocks(); + AlarmAlertWakeLock.release(); } // Dismiss the alarm. @@ -225,7 +225,7 @@ public class AlarmAlert extends Activity { } mState = DISMISS; mKlaxon.stop(this, false); - releaseLocks(); + AlarmAlertWakeLock.release(); } /** @@ -257,7 +257,6 @@ public class AlarmAlert extends Activity { protected void onResume() { super.onResume(); if (Log.LOGV) Log.v("AlarmAlert.onResume()"); - AlarmAlertWakeLock.acquire(this); } @Override @@ -267,8 +266,8 @@ public class AlarmAlert extends Activity { // As a last resort, try to snooze if this activity is stopped. snooze(); // We might have been killed by the KillerCallback so always release - // the lock and keyguard. - releaseLocks(); + // the lock. + AlarmAlertWakeLock.release(); } @Override @@ -317,11 +316,4 @@ public class AlarmAlert extends Activity { } return super.dispatchKeyEvent(event); } - - /** - * release wake and keyguard locks - */ - private synchronized void releaseLocks() { - AlarmAlertWakeLock.release(); - } } -- cgit v1.2.3 From f47699dd4704917713004099b5e3609be48653c4 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Wed, 13 May 2009 08:27:34 -0400 Subject: Rework the AlarmAlert dialog layout. Make the layout portrait only to make it more uniform and nicer looking. Update all the clock layouts to have the same size. The alert will have a larger clock than the AlarmClock screen. Remove the old hack to make the alert a dialog. Inflate the views with a root so the layout params will be fill_parent. Use the animate_circle resource in setBackgroundResource to clean up the code. Add a new AlarmAlertFullScreen activity which makes the alert fullscreen with the current wallpaper as the background. Query the keyguard manager during the alarm broadcast to decide which activity to launch. The regular alert activity shows above the current activity but the fullscreen activity will not show the previous activity for security reasons. Add the alarm_alert style that mimics the Theme.Dialog.Alert style but does not rely on a private resource. Add dialog.9.png for the same reason. Add a LinearLayout surrounding the alert layout to hold the background wallpaper. --- src/com/android/alarmclock/AlarmAlert.java | 56 +++++++++------------- .../android/alarmclock/AlarmAlertFullScreen.java | 43 +++++++++++++++++ src/com/android/alarmclock/AlarmAlertWakeLock.java | 41 ++++++++++++---- src/com/android/alarmclock/AlarmClock.java | 10 ++-- src/com/android/alarmclock/AlarmReceiver.java | 14 +++++- src/com/android/alarmclock/DigitalClock.java | 11 +---- 6 files changed, 115 insertions(+), 60 deletions(-) create mode 100644 src/com/android/alarmclock/AlarmAlertFullScreen.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 3677ce7a1..2a05abc8c 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -59,39 +59,18 @@ public class AlarmAlert extends Activity { // Maintain a lock during the playback of the alarm. This lock may have // already been acquired in AlarmReceiver. If the process was killed, // the global wake lock is gone. Acquire again just to be sure. - AlarmAlertWakeLock.acquire(this); + AlarmAlertWakeLock.acquireCpuWakeLock(this); /* FIXME Intentionally verbose: always log this until we've fully debugged the app failing to start up */ Log.v("AlarmAlert.onCreate()"); - // Popup alert over black screen - WindowManager.LayoutParams lp = getWindow().getAttributes(); - lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; - lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; - // XXX DO NOT COPY THIS!!! THIS IS BOGUS! Making an activity have - // a system alert type is completely broken, because the activity - // manager will still hide/show it as if it is part of the normal - // activity stack. If this is really what you want and you want it - // to work correctly, you should create and show your own custom window. - lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; - lp.token = null; - Window win = getWindow(); - win.setAttributes(lp); - // TODO Make the activity full screen for FLAG_SHOW_WHEN_LOCKED to bypass - // the key guard. - win.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - Intent i = getIntent(); mAlarmId = i.getIntExtra(Alarms.ID, -1); mKlaxon = new AlarmKlaxon(); mKlaxon.postPlay(this, mAlarmId); - /* Set the title from the passed in label */ - setTitleFromIntent(i); - /* allow next alarm to trigger while this activity is active */ Alarms.disableSnoozeAlert(AlarmAlert.this); @@ -114,6 +93,8 @@ public class AlarmAlert extends Activity { } }); + requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); updateLayout(); } @@ -122,7 +103,8 @@ public class AlarmAlert extends Activity { if (mLabel == null || mLabel.length() == 0) { mLabel = getString(R.string.default_label); } - setTitle(mLabel); + TextView title = (TextView) findViewById(R.id.alertTitle); + title.setText(mLabel); } private void updateSilencedText() { @@ -132,21 +114,27 @@ public class AlarmAlert extends Activity { silenced.setVisibility(View.VISIBLE); } + // This method is overwritten in AlarmAlertFullScreen in order to show a + // full activity with the wallpaper as the background. + protected View inflateView(LayoutInflater inflater) { + return inflater.inflate(R.layout.alarm_alert, null); + } + private void updateLayout() { - setContentView(R.layout.alarm_alert); + LayoutInflater inflater = LayoutInflater.from(this); + + setContentView(inflateView(inflater)); /* set clock face */ - LayoutInflater mFactory = LayoutInflater.from(this); SharedPreferences settings = getSharedPreferences(AlarmClock.PREFERENCES, 0); int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); if (face < 0 || face >= AlarmClock.CLOCKS.length) { face = 0; } - View clockLayout = - (View) mFactory.inflate(AlarmClock.CLOCKS[face], null); ViewGroup clockView = (ViewGroup) findViewById(R.id.clockView); - clockView.addView(clockLayout); + inflater.inflate(AlarmClock.CLOCKS[face], clockView); + View clockLayout = findViewById(R.id.clock); if (clockLayout instanceof DigitalClock) { ((DigitalClock) clockLayout).setAnimate(); } @@ -177,6 +165,9 @@ public class AlarmAlert extends Activity { finish(); } }); + + /* Set the title from the passed in label */ + setTitleFromIntent(getIntent()); } // Attempt to snooze this alert. @@ -257,6 +248,9 @@ public class AlarmAlert extends Activity { protected void onResume() { super.onResume(); if (Log.LOGV) Log.v("AlarmAlert.onResume()"); + // Acquire a separate lock for the screen to stay on. This is necessary + // to avoid flashing the keyguard when the screen is locked. + AlarmAlertWakeLock.acquireScreenWakeLock(this); } @Override @@ -270,12 +264,6 @@ public class AlarmAlert extends Activity { AlarmAlertWakeLock.release(); } - @Override - public void onConfigurationChanged(Configuration config) { - super.onConfigurationChanged(config); - updateLayout(); - } - @Override public boolean dispatchKeyEvent(KeyEvent event) { // Do this on key down to handle a few of the system keys. Only handle diff --git a/src/com/android/alarmclock/AlarmAlertFullScreen.java b/src/com/android/alarmclock/AlarmAlertFullScreen.java new file mode 100644 index 000000000..07d0d9c60 --- /dev/null +++ b/src/com/android/alarmclock/AlarmAlertFullScreen.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009 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.alarmclock; + +import android.graphics.drawable.BitmapDrawable; +import android.view.View; +import android.view.Gravity; +import android.view.LayoutInflater; + +/** + * Full screen alarm alert: pops visible indicator and plays alarm tone. This + * activity displays the alert in full screen in order to be secure. The + * background is the current wallpaper. + */ +public class AlarmAlertFullScreen extends AlarmAlert { + + @Override + final protected View inflateView(LayoutInflater inflater) { + View v = inflater.inflate(R.layout.alarm_alert, null); + + // Display the wallpaper as the background. + BitmapDrawable wallpaper = (BitmapDrawable) getWallpaper(); + wallpaper.setGravity(Gravity.CENTER); + v.setBackgroundDrawable(wallpaper); + + return v; + } + +} diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java index 10e5b54e4..0291df8b8 100644 --- a/src/com/android/alarmclock/AlarmAlertWakeLock.java +++ b/src/com/android/alarmclock/AlarmAlertWakeLock.java @@ -25,29 +25,50 @@ import android.os.PowerManager; */ class AlarmAlertWakeLock { - private static PowerManager.WakeLock sWakeLock; + private static PowerManager.WakeLock sScreenWakeLock; + private static PowerManager.WakeLock sCpuWakeLock; - static void acquire(Context context) { - Log.v("Acquiring wake lock"); - if (sWakeLock != null) { - sWakeLock.release(); + static void acquireCpuWakeLock(Context context) { + Log.v("Acquiring cpu wake lock"); + if (sCpuWakeLock != null) { + return; } PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - sWakeLock = pm.newWakeLock( + sCpuWakeLock = pm.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK | + PowerManager.ACQUIRE_CAUSES_WAKEUP | + PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); + sCpuWakeLock.acquire(); + } + + static void acquireScreenWakeLock(Context context) { + Log.v("Acquiring screen wake lock"); + if (sScreenWakeLock != null) { + return; + } + + PowerManager pm = + (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + sScreenWakeLock = pm.newWakeLock( PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); - sWakeLock.acquire(); + sScreenWakeLock.acquire(); } static void release() { Log.v("Releasing wake lock"); - if (sWakeLock != null) { - sWakeLock.release(); - sWakeLock = null; + if (sCpuWakeLock != null) { + sCpuWakeLock.release(); + sCpuWakeLock = null; + } + if (sScreenWakeLock != null) { + sScreenWakeLock.release(); + sScreenWakeLock = null; } } } diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index f003aac2f..d1dabb2b5 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -287,10 +287,12 @@ public class AlarmClock extends Activity implements OnItemClickListener { if (mClock != null) { mClockLayout.removeView(mClock); } - mClock = mFactory.inflate(CLOCKS[mFace], null); - TextView am = (TextView) mClock.findViewById(R.id.am); - TextView pm = (TextView) mClock.findViewById(R.id.pm); + LayoutInflater.from(this).inflate(CLOCKS[mFace], mClockLayout); + mClock = findViewById(R.id.clock); + + TextView am = (TextView) findViewById(R.id.am); + TextView pm = (TextView) findViewById(R.id.pm); if (am != null) { am.setText(mAm); @@ -298,8 +300,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { if (pm != null) { pm.setText(mPm); } - - mClockLayout.addView(mClock, 0); } @Override diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 4982f95b8..eed76b6ce 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -16,6 +16,7 @@ package com.android.alarmclock; +import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; import android.content.BroadcastReceiver; @@ -49,15 +50,24 @@ public class AlarmReceiver extends BroadcastReceiver { /* Wake the device and stay awake until the AlarmAlert intent is * handled. */ - AlarmAlertWakeLock.acquire(context); + AlarmAlertWakeLock.acquireCpuWakeLock(context); /* Close dialogs and window shade */ Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.sendBroadcast(i); + // Decide which activity to start based on the state of the keyguard. + Class c = AlarmAlert.class; + KeyguardManager km = (KeyguardManager) context.getSystemService( + Context.KEYGUARD_SERVICE); + if (km.inKeyguardRestrictedInputMode()) { + // Use the full screen activity for security. + c = AlarmAlertFullScreen.class; + } + /* launch UI, explicitly stating that this is not due to user action * so that the current app's notification management is not disturbed */ - Intent fireAlarm = new Intent(context, AlarmAlert.class); + Intent fireAlarm = new Intent(context, c); fireAlarm.putExtra(Alarms.ID, id); fireAlarm.putExtra(Alarms.LABEL, intent.getStringExtra(Alarms.LABEL)); fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); diff --git a/src/com/android/alarmclock/DigitalClock.java b/src/com/android/alarmclock/DigitalClock.java index 65dcc2c9c..d131066e1 100644 --- a/src/com/android/alarmclock/DigitalClock.java +++ b/src/com/android/alarmclock/DigitalClock.java @@ -130,16 +130,9 @@ public class DigitalClock extends LinearLayout { mAttached = true; if (mAnimate) { - AnimationDrawable frameAnimation = - (AnimationDrawable) mContext.getResources().getDrawable( - R.drawable.animate_circle); - View digitalClock = findViewById(R.id.digitalClock); - digitalClock.setBackgroundDrawable(frameAnimation); + setBackgroundResource(R.drawable.animate_circle); /* Start the animation (looped playback by default). */ - ((AnimationDrawable) digitalClock.getBackground()).start(); - /* No luck wrapping animation or individual bitmaps in a - ScaleDrawable */ - digitalClock.requestLayout(); + ((AnimationDrawable) getBackground()).start(); } if (mLive) { -- cgit v1.2.3 From e2285895da5c1adc3259948c1ea41d339d88a57c Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Thu, 18 Jun 2009 09:25:48 -0400 Subject: Merge the fullscreen change from master to donut for security. Rework the AlarmAlert dialog layout. Make the layout portrait only to make it more uniform and nicer looking. Update all the clock layouts to have the same size. The alert will have a larger clock than the AlarmClock screen. Remove the old hack to make the alert a dialog. Inflate the views with a root so the layout params will be fill_parent. Use the animate_circle resource in setBackgroundResource to clean up the code. Add a new AlarmAlertFullScreen activity which makes the alert fullscreen with the current wallpaper as the background. Query the keyguard manager during the alarm broadcast to decide which activity to launch. The regular alert activity shows above the current activity but the fullscreen activity will not show the previous activity for security reasons. Add the alarm_alert style that mimics the Theme.Dialog.Alert style but does not rely on a private resource. Add dialog.9.png for the same reason. Add a LinearLayout surrounding the alert layout to hold the background wallpaper. --- src/com/android/alarmclock/AlarmAlert.java | 57 +++++++++------------- .../android/alarmclock/AlarmAlertFullScreen.java | 43 ++++++++++++++++ src/com/android/alarmclock/AlarmAlertWakeLock.java | 41 ++++++++++++---- src/com/android/alarmclock/AlarmClock.java | 10 ++-- src/com/android/alarmclock/AlarmReceiver.java | 14 +++++- src/com/android/alarmclock/DigitalClock.java | 11 +---- 6 files changed, 115 insertions(+), 61 deletions(-) create mode 100644 src/com/android/alarmclock/AlarmAlertFullScreen.java (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 6db9aad64..a3096188a 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -58,39 +58,18 @@ public class AlarmAlert extends Activity { // Maintain a lock during the playback of the alarm. This lock may have // already been acquired in AlarmReceiver. If the process was killed, // the global wake lock is gone. Acquire again just to be sure. - // AlarmAlertWakeLock.acquire(this); + AlarmAlertWakeLock.acquireCpuWakeLock(this); /* FIXME Intentionally verbose: always log this until we've fully debugged the app failing to start up */ Log.v("AlarmAlert.onCreate()"); - // Popup alert over black screen - WindowManager.LayoutParams lp = getWindow().getAttributes(); - lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; - lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; - // XXX DO NOT COPY THIS!!! THIS IS BOGUS! Making an activity have - // a system alert type is completely broken, because the activity - // manager will still hide/show it as if it is part of the normal - // activity stack. If this is really what you want and you want it - // to work correctly, you should create and show your own custom window. - lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; - lp.token = null; - Window win = getWindow(); - win.setAttributes(lp); - // TODO Make the activity full screen for FLAG_SHOW_WHEN_LOCKED to bypass - // the key guard. - win.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - Intent i = getIntent(); mAlarmId = i.getIntExtra(Alarms.ID, -1); mKlaxon = new AlarmKlaxon(); mKlaxon.postPlay(this, mAlarmId); - /* Set the title from the passed in label */ - setTitleFromIntent(i); - /* allow next alarm to trigger while this activity is active */ Alarms.disableSnoozeAlert(AlarmAlert.this); @@ -113,6 +92,8 @@ public class AlarmAlert extends Activity { } }); + requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); updateLayout(); } @@ -121,7 +102,8 @@ public class AlarmAlert extends Activity { if (mLabel == null || mLabel.length() == 0) { mLabel = getString(R.string.default_label); } - setTitle(mLabel); + TextView title = (TextView) findViewById(R.id.alertTitle); + title.setText(mLabel); } private void updateSilencedText() { @@ -131,21 +113,27 @@ public class AlarmAlert extends Activity { silenced.setVisibility(View.VISIBLE); } + // This method is overwritten in AlarmAlertFullScreen in order to show a + // full activity with the wallpaper as the background. + protected View inflateView(LayoutInflater inflater) { + return inflater.inflate(R.layout.alarm_alert, null); + } + private void updateLayout() { - setContentView(R.layout.alarm_alert); + LayoutInflater inflater = LayoutInflater.from(this); + + setContentView(inflateView(inflater)); /* set clock face */ - LayoutInflater mFactory = LayoutInflater.from(this); SharedPreferences settings = getSharedPreferences(AlarmClock.PREFERENCES, 0); int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); if (face < 0 || face >= AlarmClock.CLOCKS.length) { face = 0; } - View clockLayout = - (View) mFactory.inflate(AlarmClock.CLOCKS[face], null); ViewGroup clockView = (ViewGroup) findViewById(R.id.clockView); - clockView.addView(clockLayout); + inflater.inflate(AlarmClock.CLOCKS[face], clockView); + View clockLayout = findViewById(R.id.clock); if (clockLayout instanceof DigitalClock) { ((DigitalClock) clockLayout).setAnimate(); } @@ -176,6 +164,9 @@ public class AlarmAlert extends Activity { finish(); } }); + + /* Set the title from the passed in label */ + setTitleFromIntent(getIntent()); } // Attempt to snooze this alert. @@ -251,7 +242,9 @@ public class AlarmAlert extends Activity { protected void onResume() { super.onResume(); if (Log.LOGV) Log.v("AlarmAlert.onResume()"); - AlarmAlertWakeLock.acquire(this); + // Acquire a separate lock for the screen to stay on. This is necessary + // to avoid flashing the keyguard when the screen is locked. + AlarmAlertWakeLock.acquireScreenWakeLock(this); } @Override @@ -265,12 +258,6 @@ public class AlarmAlert extends Activity { releaseLocks(); } - @Override - public void onConfigurationChanged(Configuration config) { - super.onConfigurationChanged(config); - updateLayout(); - } - @Override public boolean dispatchKeyEvent(KeyEvent event) { // Do this on key down to handle a few of the system keys. Only handle diff --git a/src/com/android/alarmclock/AlarmAlertFullScreen.java b/src/com/android/alarmclock/AlarmAlertFullScreen.java new file mode 100644 index 000000000..07d0d9c60 --- /dev/null +++ b/src/com/android/alarmclock/AlarmAlertFullScreen.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009 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.alarmclock; + +import android.graphics.drawable.BitmapDrawable; +import android.view.View; +import android.view.Gravity; +import android.view.LayoutInflater; + +/** + * Full screen alarm alert: pops visible indicator and plays alarm tone. This + * activity displays the alert in full screen in order to be secure. The + * background is the current wallpaper. + */ +public class AlarmAlertFullScreen extends AlarmAlert { + + @Override + final protected View inflateView(LayoutInflater inflater) { + View v = inflater.inflate(R.layout.alarm_alert, null); + + // Display the wallpaper as the background. + BitmapDrawable wallpaper = (BitmapDrawable) getWallpaper(); + wallpaper.setGravity(Gravity.CENTER); + v.setBackgroundDrawable(wallpaper); + + return v; + } + +} diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java index 10e5b54e4..0291df8b8 100644 --- a/src/com/android/alarmclock/AlarmAlertWakeLock.java +++ b/src/com/android/alarmclock/AlarmAlertWakeLock.java @@ -25,29 +25,50 @@ import android.os.PowerManager; */ class AlarmAlertWakeLock { - private static PowerManager.WakeLock sWakeLock; + private static PowerManager.WakeLock sScreenWakeLock; + private static PowerManager.WakeLock sCpuWakeLock; - static void acquire(Context context) { - Log.v("Acquiring wake lock"); - if (sWakeLock != null) { - sWakeLock.release(); + static void acquireCpuWakeLock(Context context) { + Log.v("Acquiring cpu wake lock"); + if (sCpuWakeLock != null) { + return; } PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - sWakeLock = pm.newWakeLock( + sCpuWakeLock = pm.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK | + PowerManager.ACQUIRE_CAUSES_WAKEUP | + PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); + sCpuWakeLock.acquire(); + } + + static void acquireScreenWakeLock(Context context) { + Log.v("Acquiring screen wake lock"); + if (sScreenWakeLock != null) { + return; + } + + PowerManager pm = + (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + sScreenWakeLock = pm.newWakeLock( PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); - sWakeLock.acquire(); + sScreenWakeLock.acquire(); } static void release() { Log.v("Releasing wake lock"); - if (sWakeLock != null) { - sWakeLock.release(); - sWakeLock = null; + if (sCpuWakeLock != null) { + sCpuWakeLock.release(); + sCpuWakeLock = null; + } + if (sScreenWakeLock != null) { + sScreenWakeLock.release(); + sScreenWakeLock = null; } } } diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index 24f41d145..9804994d8 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -278,10 +278,12 @@ public class AlarmClock extends Activity { if (mClock != null) { mClockLayout.removeView(mClock); } - mClock = mFactory.inflate(CLOCKS[mFace], null); - TextView am = (TextView) mClock.findViewById(R.id.am); - TextView pm = (TextView) mClock.findViewById(R.id.pm); + LayoutInflater.from(this).inflate(CLOCKS[mFace], mClockLayout); + mClock = findViewById(R.id.clock); + + TextView am = (TextView) findViewById(R.id.am); + TextView pm = (TextView) findViewById(R.id.pm); if (am != null) { am.setText(mAm); @@ -289,8 +291,6 @@ public class AlarmClock extends Activity { if (pm != null) { pm.setText(mPm); } - - mClockLayout.addView(mClock, 0); } @Override diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 4982f95b8..eed76b6ce 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -16,6 +16,7 @@ package com.android.alarmclock; +import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; import android.content.BroadcastReceiver; @@ -49,15 +50,24 @@ public class AlarmReceiver extends BroadcastReceiver { /* Wake the device and stay awake until the AlarmAlert intent is * handled. */ - AlarmAlertWakeLock.acquire(context); + AlarmAlertWakeLock.acquireCpuWakeLock(context); /* Close dialogs and window shade */ Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.sendBroadcast(i); + // Decide which activity to start based on the state of the keyguard. + Class c = AlarmAlert.class; + KeyguardManager km = (KeyguardManager) context.getSystemService( + Context.KEYGUARD_SERVICE); + if (km.inKeyguardRestrictedInputMode()) { + // Use the full screen activity for security. + c = AlarmAlertFullScreen.class; + } + /* launch UI, explicitly stating that this is not due to user action * so that the current app's notification management is not disturbed */ - Intent fireAlarm = new Intent(context, AlarmAlert.class); + Intent fireAlarm = new Intent(context, c); fireAlarm.putExtra(Alarms.ID, id); fireAlarm.putExtra(Alarms.LABEL, intent.getStringExtra(Alarms.LABEL)); fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); diff --git a/src/com/android/alarmclock/DigitalClock.java b/src/com/android/alarmclock/DigitalClock.java index 65dcc2c9c..d131066e1 100644 --- a/src/com/android/alarmclock/DigitalClock.java +++ b/src/com/android/alarmclock/DigitalClock.java @@ -130,16 +130,9 @@ public class DigitalClock extends LinearLayout { mAttached = true; if (mAnimate) { - AnimationDrawable frameAnimation = - (AnimationDrawable) mContext.getResources().getDrawable( - R.drawable.animate_circle); - View digitalClock = findViewById(R.id.digitalClock); - digitalClock.setBackgroundDrawable(frameAnimation); + setBackgroundResource(R.drawable.animate_circle); /* Start the animation (looped playback by default). */ - ((AnimationDrawable) digitalClock.getBackground()).start(); - /* No luck wrapping animation or individual bitmaps in a - ScaleDrawable */ - digitalClock.requestLayout(); + ((AnimationDrawable) getBackground()).start(); } if (mLive) { -- cgit v1.2.3 From d776e51cec79901a0d656bba7b1f3780cceac57d Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Fri, 26 Jun 2009 14:52:56 -0400 Subject: Huge rewrite of the AlarmClock to play the Alarm in a service. The AlarmKlaxon has been converted to a service that plays the alarm and vibrates the device. The AlarmAlert now just shows the UI for the alarm and allows the user to snooze or dismiss the alarm. The snooze button must be pressed in order to snooze the alarm. Volume and Camera buttons dismiss the alarm while other buttons have their original behavior. Each alarm triggers a notification that the alarm has fired. This allows another activity (say, the Calendar alert #1908616) to play on top of the AlarmAlert. The AlarmKlaxon service will continue to play even though the alert has been dismissed. The user can get back to the UI through the notification. If the user snoozes the alarm, the notification reflects that choice (#1691034) and allows the user to cancel the snooze by clicking the notification. The snoozed alarm takes priority over any other alarm (#1693155) so that it will play unless the notification is clicked. The database interaction has also been rewritten to use a Parcelable Alarm class for sending and receiving the alarm data in a much simpler manner. This allows for fewer database lookups since each activity no longer has to lookup the alarm info. The alarm silenced text has been removed from the AlarmAlert UI and moved to the notification area. When an alarm is killed, the alert is dismissed and the notification reflects the state. Clicking the notification launches the SetAlarm activity so the user can see which alarm was killed. --- src/com/android/alarmclock/Alarm.java | 341 ++++++++++ src/com/android/alarmclock/AlarmAlert.java | 256 +++----- src/com/android/alarmclock/AlarmAlertWakeLock.java | 8 +- src/com/android/alarmclock/AlarmClock.java | 72 +-- src/com/android/alarmclock/AlarmInitReceiver.java | 2 +- src/com/android/alarmclock/AlarmKlaxon.java | 152 +++-- src/com/android/alarmclock/AlarmProvider.java | 36 +- src/com/android/alarmclock/AlarmReceiver.java | 160 ++++- src/com/android/alarmclock/Alarms.java | 683 ++++++--------------- src/com/android/alarmclock/RepeatPreference.java | 8 +- src/com/android/alarmclock/SetAlarm.java | 81 +-- 11 files changed, 924 insertions(+), 875 deletions(-) create mode 100644 src/com/android/alarmclock/Alarm.java (limited to 'src') diff --git a/src/com/android/alarmclock/Alarm.java b/src/com/android/alarmclock/Alarm.java new file mode 100644 index 000000000..350b7b496 --- /dev/null +++ b/src/com/android/alarmclock/Alarm.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2009 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.alarmclock; + +import android.content.Context; +import android.database.Cursor; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.BaseColumns; +import android.text.format.DateFormat; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +public final class Alarm implements Parcelable { + + ////////////////////////////// + // Parcelable apis + ////////////////////////////// + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public Alarm createFromParcel(Parcel p) { + return new Alarm(p); + } + + public Alarm[] newArray(int size) { + return new Alarm[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel p, int flags) { + p.writeInt(id); + p.writeInt(enabled ? 1 : 0); + p.writeInt(hour); + p.writeInt(minutes); + p.writeInt(daysOfWeek.getCoded()); + p.writeLong(time); + p.writeInt(vibrate ? 1 : 0); + p.writeString(label); + p.writeParcelable(alert, flags); + p.writeInt(silent ? 1 : 0); + } + ////////////////////////////// + // end Parcelable apis + ////////////////////////////// + + ////////////////////////////// + // Column definitions + ////////////////////////////// + public static class Columns implements BaseColumns { + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://com.android.alarmclock/alarm"); + + /** + * Hour in 24-hour localtime 0 - 23. + *

Type: INTEGER

+ */ + public static final String HOUR = "hour"; + + /** + * Minutes in localtime 0 - 59 + *

Type: INTEGER

+ */ + public static final String MINUTES = "minutes"; + + /** + * Days of week coded as integer + *

Type: INTEGER

+ */ + public static final String DAYS_OF_WEEK = "daysofweek"; + + /** + * Alarm time in UTC milliseconds from the epoch. + *

Type: INTEGER

+ */ + public static final String ALARM_TIME = "alarmtime"; + + /** + * True if alarm is active + *

Type: BOOLEAN

+ */ + public static final String ENABLED = "enabled"; + + /** + * True if alarm should vibrate + *

Type: BOOLEAN

+ */ + public static final String VIBRATE = "vibrate"; + + /** + * Message to show when alarm triggers + * Note: not currently used + *

Type: STRING

+ */ + public static final String MESSAGE = "message"; + + /** + * Audio alert to play when alarm triggers + *

Type: STRING

+ */ + public static final String ALERT = "alert"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = _ID + " ASC"; + + // Used when filtering enabled alarms. + public static final String WHERE_ENABLED = ENABLED + "=1"; + + static final String[] ALARM_QUERY_COLUMNS = { + _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME, + ENABLED, VIBRATE, MESSAGE, ALERT }; + + /** + * These save calls to cursor.getColumnIndexOrThrow() + * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS + */ + public static final int ALARM_ID_INDEX = 0; + public static final int ALARM_HOUR_INDEX = 1; + public static final int ALARM_MINUTES_INDEX = 2; + public static final int ALARM_DAYS_OF_WEEK_INDEX = 3; + public static final int ALARM_TIME_INDEX = 4; + public static final int ALARM_ENABLED_INDEX = 5; + public static final int ALARM_VIBRATE_INDEX = 6; + public static final int ALARM_MESSAGE_INDEX = 7; + public static final int ALARM_ALERT_INDEX = 8; + } + ////////////////////////////// + // End column definitions + ////////////////////////////// + + // Public fields + public int id; + public boolean enabled; + public int hour; + public int minutes; + public DaysOfWeek daysOfWeek; + public long time; + public boolean vibrate; + public String label; + public Uri alert; + public boolean silent; + + public Alarm(Cursor c) { + id = c.getInt(Columns.ALARM_ID_INDEX); + enabled = c.getInt(Columns.ALARM_ENABLED_INDEX) == 1; + hour = c.getInt(Columns.ALARM_HOUR_INDEX); + minutes = c.getInt(Columns.ALARM_MINUTES_INDEX); + daysOfWeek = new DaysOfWeek(c.getInt(Columns.ALARM_DAYS_OF_WEEK_INDEX)); + time = c.getLong(Columns.ALARM_TIME_INDEX); + vibrate = c.getInt(Columns.ALARM_VIBRATE_INDEX) == 1; + label = c.getString(Columns.ALARM_MESSAGE_INDEX); + String alertString = c.getString(Columns.ALARM_ALERT_INDEX); + if (Alarms.ALARM_ALERT_SILENT.equals(alertString)) { + if (Log.LOGV) { + Log.v("Alarm is marked as silent"); + } + silent = true; + } else { + if (alertString != null && alertString.length() != 0) { + alert = Uri.parse(alertString); + } + + // If the database alert is null or it failed to parse, use the + // default alert. + if (alert == null) { + alert = RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_ALARM); + } + } + } + + public Alarm(Parcel p) { + id = p.readInt(); + enabled = p.readInt() == 1; + hour = p.readInt(); + minutes = p.readInt(); + daysOfWeek = new DaysOfWeek(p.readInt()); + time = p.readLong(); + vibrate = p.readInt() == 1; + label = p.readString(); + alert = (Uri) p.readParcelable(null); + silent = p.readInt() == 1; + } + + public String getLabelOrDefault(Context context) { + if (label == null || label.length() == 0) { + return context.getString(R.string.default_label); + } + return label; + } + + /* + * Days of week code as a single int. + * 0x00: no day + * 0x01: Monday + * 0x02: Tuesday + * 0x04: Wednesday + * 0x08: Thursday + * 0x10: Friday + * 0x20: Saturday + * 0x40: Sunday + */ + static final class DaysOfWeek { + + private static int[] DAY_MAP = new int[] { + Calendar.MONDAY, + Calendar.TUESDAY, + Calendar.WEDNESDAY, + Calendar.THURSDAY, + Calendar.FRIDAY, + Calendar.SATURDAY, + Calendar.SUNDAY, + }; + + // Bitmask of all repeating days + private int mDays; + + DaysOfWeek(int days) { + mDays = days; + } + + public String toString(Context context, boolean showNever) { + StringBuilder ret = new StringBuilder(); + + // no days + if (mDays == 0) { + return showNever ? + context.getText(R.string.never).toString() : ""; + } + + // every day + if (mDays == 0x7f) { + return context.getText(R.string.every_day).toString(); + } + + // count selected days + int dayCount = 0, days = mDays; + while (days > 0) { + if ((days & 1) == 1) dayCount++; + days >>= 1; + } + + // short or long form? + DateFormatSymbols dfs = new DateFormatSymbols(); + String[] dayList = (dayCount > 1) ? + dfs.getShortWeekdays() : + dfs.getWeekdays(); + + // selected days + for (int i = 0; i < 7; i++) { + if ((mDays & (1 << i)) != 0) { + ret.append(dayList[DAY_MAP[i]]); + dayCount -= 1; + if (dayCount > 0) ret.append( + context.getText(R.string.day_concat)); + } + } + return ret.toString(); + } + + private boolean isSet(int day) { + return ((mDays & (1 << day)) > 0); + } + + public void set(int day, boolean set) { + if (set) { + mDays |= (1 << day); + } else { + mDays &= ~(1 << day); + } + } + + public void set(DaysOfWeek dow) { + mDays = dow.mDays; + } + + public int getCoded() { + return mDays; + } + + // Returns days of week encoded in an array of booleans. + public boolean[] getBooleanArray() { + boolean[] ret = new boolean[7]; + for (int i = 0; i < 7; i++) { + ret[i] = isSet(i); + } + return ret; + } + + public boolean isRepeatSet() { + return mDays != 0; + } + + /** + * returns number of days from today until next alarm + * @param c must be set to today + */ + public int getNextAlarm(Calendar c) { + if (mDays == 0) { + return -1; + } + + int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7; + + int day = 0; + int dayCount = 0; + for (; dayCount < 7; dayCount++) { + day = (today + dayCount) % 7; + if (isSet(day)) { + break; + } + } + return dayCount; + } + } +} diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 2a05abc8c..3567d87ac 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -17,7 +17,13 @@ package com.android.alarmclock; import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.BroadcastReceiver; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Bundle; @@ -41,77 +47,37 @@ import java.util.Calendar; public class AlarmAlert extends Activity { private static final String DEFAULT_SNOOZE = "10"; - private static final int UNKNOWN = 0; - private static final int SNOOZE = 1; - private static final int DISMISS = 2; - private static final int KILLED = 3; - private Button mSnoozeButton; - private int mState = UNKNOWN; - - private AlarmKlaxon mKlaxon; - private int mAlarmId; - private String mLabel; + private Alarm mAlarm; + + // Receives the ALARM_KILLED action from the AlarmKlaxon. + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Alarm alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); + if (mAlarm.id == alarm.id) { + dismiss(true); + } + } + }; @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - // Maintain a lock during the playback of the alarm. This lock may have - // already been acquired in AlarmReceiver. If the process was killed, - // the global wake lock is gone. Acquire again just to be sure. - AlarmAlertWakeLock.acquireCpuWakeLock(this); - - /* FIXME Intentionally verbose: always log this until we've - fully debugged the app failing to start up */ - Log.v("AlarmAlert.onCreate()"); - - Intent i = getIntent(); - mAlarmId = i.getIntExtra(Alarms.ID, -1); - - mKlaxon = new AlarmKlaxon(); - mKlaxon.postPlay(this, mAlarmId); - - /* allow next alarm to trigger while this activity is - active */ - Alarms.disableSnoozeAlert(AlarmAlert.this); - Alarms.disableAlert(AlarmAlert.this, mAlarmId); - Alarms.setNextAlert(this); - - mKlaxon.setKillerCallback(new AlarmKlaxon.KillerCallback() { - public void onKilled() { - if (Log.LOGV) Log.v("onKilled()"); - updateSilencedText(); - - /* don't allow snooze */ - mSnoozeButton.setEnabled(false); - - // Dismiss the alarm but mark the state as killed so if the - // config changes, we show the silenced message and disable - // snooze. - dismiss(); - mState = KILLED; - } - }); + mAlarm = getIntent().getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); updateLayout(); - } - private void setTitleFromIntent(Intent i) { - mLabel = i.getStringExtra(Alarms.LABEL); - if (mLabel == null || mLabel.length() == 0) { - mLabel = getString(R.string.default_label); - } - TextView title = (TextView) findViewById(R.id.alertTitle); - title.setText(mLabel); + // Register to get the alarm killed intent. + registerReceiver(mReceiver, new IntentFilter(Alarms.ALARM_KILLED)); } - private void updateSilencedText() { - TextView silenced = (TextView) findViewById(R.id.silencedText); - silenced.setText(getString(R.string.alarm_alert_alert_silenced, - AlarmKlaxon.ALARM_TIMEOUT_SECONDS / 60)); - silenced.setVisibility(View.VISIBLE); + private void setTitle() { + String label = mAlarm.getLabelOrDefault(this); + TextView title = (TextView) findViewById(R.id.alertTitle); + title.setText(label); } // This method is overwritten in AlarmAlertFullScreen in order to show a @@ -141,42 +107,28 @@ public class AlarmAlert extends Activity { /* snooze behavior: pop a snooze confirmation view, kick alarm manager. */ - mSnoozeButton = (Button) findViewById(R.id.snooze); - mSnoozeButton.requestFocus(); - // If this was a configuration change, keep the silenced text if the - // alarm was killed. - if (mState == KILLED) { - updateSilencedText(); - mSnoozeButton.setEnabled(false); - } else { - mSnoozeButton.setOnClickListener(new Button.OnClickListener() { - public void onClick(View v) { - snooze(); - finish(); - } - }); - } + Button snooze = (Button) findViewById(R.id.snooze); + snooze.requestFocus(); + snooze.setOnClickListener(new Button.OnClickListener() { + public void onClick(View v) { + snooze(); + } + }); /* dismiss button: close notification */ findViewById(R.id.dismiss).setOnClickListener( new Button.OnClickListener() { public void onClick(View v) { - dismiss(); - finish(); + dismiss(false); } }); - /* Set the title from the passed in label */ - setTitleFromIntent(getIntent()); + /* Set the title from the passed in alarm */ + setTitle(); } // Attempt to snooze this alert. private void snooze() { - if (mState != UNKNOWN) { - return; - } - // If the next alarm is set for sooner than the snooze interval, don't - // snooze. Instead, toast the user that the snooze will not be set. final String snooze = PreferenceManager.getDefaultSharedPreferences(this) .getString("snooze_duration", DEFAULT_SNOOZE); @@ -184,39 +136,58 @@ public class AlarmAlert extends Activity { final long snoozeTime = System.currentTimeMillis() + (1000 * 60 * snoozeMinutes); - final long nextAlarm = - Alarms.calculateNextAlert(AlarmAlert.this).getAlert(); - String displayTime = null; - if (nextAlarm < snoozeTime) { - final Calendar c = Calendar.getInstance(); - c.setTimeInMillis(nextAlarm); - displayTime = getString(R.string.alarm_alert_snooze_not_set, - Alarms.formatTime(AlarmAlert.this, c)); - mState = DISMISS; - } else { - Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarmId, snoozeTime, - mLabel); - Alarms.setNextAlert(AlarmAlert.this); - displayTime = getString(R.string.alarm_alert_snooze_set, - snoozeMinutes); - mState = SNOOZE; - } + Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarm.id, snoozeTime); + + // Get the display time for the snooze and update the notification. + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(snoozeTime); + + // Append (snoozed) to the label. + String label = mAlarm.getLabelOrDefault(this); + label = getString(R.string.alarm_notify_snooze_label, label); + + // Notify the user that the alarm has been snoozed. + Intent cancelSnooze = new Intent(this, AlarmReceiver.class); + cancelSnooze.setAction(Alarms.CANCEL_SNOOZE); + cancelSnooze.putExtra(Alarms.ALARM_ID, mAlarm.id); + PendingIntent broadcast = + PendingIntent.getBroadcast(this, mAlarm.id, cancelSnooze, 0); + NotificationManager nm = getNotificationManager(); + Notification n = new Notification(R.drawable.stat_notify_alarm, + label, 0); + n.setLatestEventInfo(this, label, + getString(R.string.alarm_notify_snooze_text, + Alarms.formatTime(this, c)), broadcast); + n.deleteIntent = broadcast; + n.flags |= Notification.FLAG_AUTO_CANCEL; + nm.notify(mAlarm.id, n); + + String displayTime = getString(R.string.alarm_alert_snooze_set, + snoozeMinutes); // Intentionally log the snooze time for debugging. Log.v(displayTime); + // Display the snooze minutes in a toast. Toast.makeText(AlarmAlert.this, displayTime, Toast.LENGTH_LONG).show(); - mKlaxon.stop(this, mState == SNOOZE); - AlarmAlertWakeLock.release(); + stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); + finish(); + } + + private NotificationManager getNotificationManager() { + return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } // Dismiss the alarm. - private void dismiss() { - if (mState != UNKNOWN) { - return; + private void dismiss(boolean killed) { + // The service told us that the alarm has been killed, do not modify + // the notification or stop the service. + if (!killed) { + // Cancel the notification and stop playing the alarm + NotificationManager nm = getNotificationManager(); + nm.cancel(mAlarm.id); + stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); } - mState = DISMISS; - mKlaxon.stop(this, false); - AlarmAlertWakeLock.release(); + finish(); } /** @@ -226,28 +197,18 @@ public class AlarmAlert extends Activity { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); - mState = UNKNOWN; - mSnoozeButton.setEnabled(true); - - mAlarmId = intent.getIntExtra(Alarms.ID, -1); - // Play the new alarm sound. - mKlaxon.postPlay(this, mAlarmId); - setTitleFromIntent(intent); + if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); - /* unset silenced message */ - TextView silenced = (TextView)findViewById(R.id.silencedText); - silenced.setVisibility(View.GONE); + mAlarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); - Alarms.setNextAlert(this); - setIntent(intent); + setTitle(); } @Override - protected void onResume() { + protected void onStart() { super.onResume(); - if (Log.LOGV) Log.v("AlarmAlert.onResume()"); + if (Log.LOGV) Log.v("AlarmAlert.onStart()"); // Acquire a separate lock for the screen to stay on. This is necessary // to avoid flashing the keyguard when the screen is locked. AlarmAlertWakeLock.acquireScreenWakeLock(this); @@ -255,52 +216,35 @@ public class AlarmAlert extends Activity { @Override protected void onStop() { - super.onStop(); + super.onPause(); if (Log.LOGV) Log.v("AlarmAlert.onStop()"); - // As a last resort, try to snooze if this activity is stopped. - snooze(); - // We might have been killed by the KillerCallback so always release - // the lock. - AlarmAlertWakeLock.release(); + AlarmAlertWakeLock.releaseScreenLock(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (Log.LOGV) Log.v("AlarmAlert.onDestroy()"); + // No longer care about the alarm being killed. + unregisterReceiver(mReceiver); } @Override public boolean dispatchKeyEvent(KeyEvent event) { - // Do this on key down to handle a few of the system keys. Only handle - // the snooze and dismiss this alert if the state is unknown. + // Do this on key down to handle a few of the system keys. boolean up = event.getAction() == KeyEvent.ACTION_UP; - boolean dismiss = false; switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_CENTER: - // Ignore ENDCALL because we do not receive the event if the screen - // is on. However, we do receive the key up for ENDCALL if the - // screen was off. - case KeyEvent.KEYCODE_ENDCALL: - break; - // Volume keys dismiss the alarm + // Volume keys and camera keys dismiss the alarm case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_CAMERA: case KeyEvent.KEYCODE_FOCUS: - dismiss = true; - // All other keys will snooze the alarm - default: - // Check for UNKNOWN here so that we intercept both key events - // and prevent the volume keys from triggering their default - // behavior. - if (mState == UNKNOWN && up) { - if (dismiss) { - dismiss(); - } else { - snooze(); - } - finish(); + if (up) { + dismiss(false); } return true; + default: + break; } return super.dispatchKeyEvent(event); } diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java index 0291df8b8..d6ab76497 100644 --- a/src/com/android/alarmclock/AlarmAlertWakeLock.java +++ b/src/com/android/alarmclock/AlarmAlertWakeLock.java @@ -60,12 +60,16 @@ class AlarmAlertWakeLock { sScreenWakeLock.acquire(); } - static void release() { - Log.v("Releasing wake lock"); + static void releaseCpuLock() { + Log.v("Releasing cpu wake lock"); if (sCpuWakeLock != null) { sCpuWakeLock.release(); sCpuWakeLock = null; } + } + + static void releaseScreenLock() { + Log.v("Releasing screen lock"); if (sScreenWakeLock != null) { sScreenWakeLock.release(); sScreenWakeLock = null; diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index d1dabb2b5..f84db846c 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -106,42 +106,36 @@ public class AlarmClock extends Activity implements OnItemClickListener { } public void bindView(View view, Context context, Cursor cursor) { - final int id = cursor.getInt(Alarms.AlarmColumns.ALARM_ID_INDEX); - final int hour = cursor.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX); - final int minutes = cursor.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX); - final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek( - cursor.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); - final boolean enabled = cursor.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1; - final String label = - cursor.getString(Alarms.AlarmColumns.ALARM_MESSAGE_INDEX); + final Alarm alarm = new Alarm(cursor); CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton); - onButton.setChecked(enabled); + onButton.setChecked(alarm.enabled); onButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { boolean isChecked = ((CheckBox) v).isChecked(); - Alarms.enableAlarm(AlarmClock.this, id, isChecked); + Alarms.enableAlarm(AlarmClock.this, alarm.id, + isChecked); if (isChecked) { - SetAlarm.popAlarmSetToast( - AlarmClock.this, hour, minutes, daysOfWeek); + SetAlarm.popAlarmSetToast(AlarmClock.this, + alarm.hour, alarm.minutes, alarm.daysOfWeek); } } }); - DigitalClock digitalClock = (DigitalClock)view.findViewById(R.id.digitalClock); - if (Log.LOGV) Log.v("bindView " + cursor.getPosition() + " " + id + " " + hour + - ":" + minutes + " " + daysOfWeek.toString(context, true) + " dc " + digitalClock); + DigitalClock digitalClock = + (DigitalClock) view.findViewById(R.id.digitalClock); // set the alarm text final Calendar c = Calendar.getInstance(); - c.set(Calendar.HOUR_OF_DAY, hour); - c.set(Calendar.MINUTE, minutes); + c.set(Calendar.HOUR_OF_DAY, alarm.hour); + c.set(Calendar.MINUTE, alarm.minutes); digitalClock.updateTime(c); // Set the repeat text or leave it blank if it does not repeat. - TextView daysOfWeekView = (TextView) digitalClock.findViewById(R.id.daysOfWeek); + TextView daysOfWeekView = + (TextView) digitalClock.findViewById(R.id.daysOfWeek); final String daysOfWeekStr = - daysOfWeek.toString(AlarmClock.this, false); + alarm.daysOfWeek.toString(AlarmClock.this, false); if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) { daysOfWeekView.setText(daysOfWeekStr); daysOfWeekView.setVisibility(View.VISIBLE); @@ -152,8 +146,8 @@ public class AlarmClock extends Activity implements OnItemClickListener { // Display the label TextView labelView = (TextView) digitalClock.findViewById(R.id.label); - if (label != null && label.length() != 0) { - labelView.setText(label); + if (alarm.label != null && alarm.label.length() != 0) { + labelView.setText(alarm.label); labelView.setVisibility(View.VISIBLE); } else { labelView.setVisibility(View.GONE); @@ -161,10 +155,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { } }; - private boolean isAlarmEnabled(final Cursor c) { - return c.getInt(Alarms.AlarmColumns.ALARM_ENABLED_INDEX) == 1; - } - @Override public boolean onContextItemSelected(final MenuItem item) { final AdapterContextMenuInfo info = @@ -190,16 +180,11 @@ public class AlarmClock extends Activity implements OnItemClickListener { case R.id.enable_alarm: final Cursor c = (Cursor) mAlarmsList.getAdapter() .getItem(info.position); - boolean enabled = isAlarmEnabled(c); - Alarms.enableAlarm(this, id, !enabled); - if (!enabled) { - final int hour = - c.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX); - final int minutes = - c.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX); - final Alarms.DaysOfWeek daysOfWeek = new Alarms.DaysOfWeek( - c.getInt(Alarms.AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); - SetAlarm.popAlarmSetToast(this, hour, minutes, daysOfWeek); + final Alarm alarm = new Alarm(c); + Alarms.enableAlarm(this, alarm.id, !alarm.enabled); + if (!alarm.enabled) { + SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes, + alarm.daysOfWeek); } return true; @@ -320,15 +305,12 @@ public class AlarmClock extends Activity implements OnItemClickListener { final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; final Cursor c = (Cursor) mAlarmsList.getAdapter().getItem((int) info.position); - final int hour = c.getInt(Alarms.AlarmColumns.ALARM_HOUR_INDEX); - final int minutes = c.getInt(Alarms.AlarmColumns.ALARM_MINUTES_INDEX); - final String label = - c.getString(Alarms.AlarmColumns.ALARM_MESSAGE_INDEX); + final Alarm alarm = new Alarm(c); // Construct the Calendar to compute the time. final Calendar cal = Calendar.getInstance(); - cal.set(Calendar.HOUR_OF_DAY, hour); - cal.set(Calendar.MINUTE, minutes); + cal.set(Calendar.HOUR_OF_DAY, alarm.hour); + cal.set(Calendar.MINUTE, alarm.minutes); final String time = Alarms.formatTime(this, cal); // Inflate the custom view and set each TextView's text. @@ -336,19 +318,19 @@ public class AlarmClock extends Activity implements OnItemClickListener { TextView textView = (TextView) v.findViewById(R.id.header_time); textView.setText(time); textView = (TextView) v.findViewById(R.id.header_label); - textView.setText(label); + textView.setText(alarm.label); // Set the custom view on the menu. menu.setHeaderView(v); // Change the text to "disable" if the alarm is already enabled. - if (isAlarmEnabled(c)) { + if (alarm.enabled) { menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm); } } public void onItemClick(AdapterView parent, View v, int pos, long id) { Intent intent = new Intent(this, SetAlarm.class); - intent.putExtra(Alarms.ID, (int) id); + intent.putExtra(Alarms.ALARM_ID, (int) id); startActivity(intent); } @@ -378,7 +360,7 @@ public class AlarmClock extends Activity implements OnItemClickListener { Log.v("In AlarmClock, new alarm id = " + newId); } Intent intent = new Intent(this, SetAlarm.class); - intent.putExtra(Alarms.ID, newId); + intent.putExtra(Alarms.ALARM_ID, newId); startActivity(intent); return true; diff --git a/src/com/android/alarmclock/AlarmInitReceiver.java b/src/com/android/alarmclock/AlarmInitReceiver.java index 77549b07a..8657e031f 100644 --- a/src/com/android/alarmclock/AlarmInitReceiver.java +++ b/src/com/android/alarmclock/AlarmInitReceiver.java @@ -36,7 +36,7 @@ public class AlarmInitReceiver extends BroadcastReceiver { return; } if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - Alarms.disableSnoozeAlert(context); + Alarms.saveSnoozeAlert(context, -1, -1); Alarms.disableExpiredAlarms(context); } Alarms.setNextAlert(context); diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index 4cf306bcf..b63486092 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -16,8 +16,9 @@ package com.android.alarmclock; -import android.content.ContentResolver; +import android.app.Service; import android.content.Context; +import android.content.Intent; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.media.AudioManager; @@ -27,6 +28,7 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.Vibrator; import android.telephony.TelephonyManager; @@ -35,29 +37,21 @@ import android.telephony.TelephonyManager; * Manages alarms and vibe. Singleton, so it can be initiated in * AlarmReceiver and shut down in the AlarmAlert activity */ -class AlarmKlaxon implements Alarms.AlarmSettings { - - interface KillerCallback { - public void onKilled(); - } +public class AlarmKlaxon extends Service { /** Play alarm up to 10 minutes before silencing */ - final static int ALARM_TIMEOUT_SECONDS = 10 * 60; + private static final int ALARM_TIMEOUT_SECONDS = 10 * 60; private static final long[] sVibratePattern = new long[] { 500, 500 }; - private int mAlarmId; - private String mAlert; - private Alarms.DaysOfWeek mDaysOfWeek; - private boolean mVibrate; private boolean mPlaying = false; private Vibrator mVibrator; private MediaPlayer mMediaPlayer; - private KillerCallback mKillerCallback; + private Alarm mCurrentAlarm; + private long mStartTime; // Internal messages private static final int KILLER = 1000; - private static final int PLAY = 1001; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { @@ -65,67 +59,80 @@ class AlarmKlaxon implements Alarms.AlarmSettings { if (Log.LOGV) { Log.v("*********** Alarm killer triggered ***********"); } - if (mKillerCallback != null) { - mKillerCallback.onKilled(); - } - break; - case PLAY: - play((Context) msg.obj, msg.arg1); + sendKillBroadcast((Alarm) msg.obj); + stopSelf(); break; } } }; - AlarmKlaxon() { + @Override + public void onCreate() { mVibrator = new Vibrator(); + AlarmAlertWakeLock.acquireCpuWakeLock(this); } - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - if (Log.LOGV) Log.v("AlarmKlaxon.reportAlarm: " + idx + " " + hour + - " " + minutes + " dow " + daysOfWeek); - mAlert = alert; - mDaysOfWeek = daysOfWeek; - mVibrate = vibrate; + @Override + public void onDestroy() { + stop(); + AlarmAlertWakeLock.releaseCpuLock(); } - public void postPlay(final Context context, final int alarmId) { - mHandler.sendMessage(mHandler.obtainMessage(PLAY, alarmId, 0, context)); + @Override + public IBinder onBind(Intent intent) { + return null; } - // Volume suggested by media team for in-call alarms. - private static final float IN_CALL_VOLUME = 0.125f; + @Override + public void onStart(Intent intent, int startId) { + final Alarm alarm = intent.getParcelableExtra( + Alarms.ALARM_INTENT_EXTRA); - private void play(Context context, int alarmId) { - ContentResolver contentResolver = context.getContentResolver(); + if (alarm == null) { + Log.v("AlarmKlaxon failed to parse the alarm from the intent"); + return; + } + + if (mCurrentAlarm != null) { + sendKillBroadcast(mCurrentAlarm); + } - if (mPlaying) stop(context, false); + play(alarm); + mCurrentAlarm = alarm; + } + + private void sendKillBroadcast(Alarm alarm) { + long millis = System.currentTimeMillis() - mStartTime; + int minutes = (int) Math.round(millis / 60000.0); + Intent alarmKilled = new Intent(Alarms.ALARM_KILLED); + alarmKilled.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); + alarmKilled.putExtra(Alarms.ALARM_KILLED_TIMEOUT, minutes); + sendBroadcast(alarmKilled); + } - mAlarmId = alarmId; + // Volume suggested by media team for in-call alarms. + private static final float IN_CALL_VOLUME = 0.125f; - /* this will call reportAlarm() callback */ - Alarms.getAlarm(contentResolver, this, mAlarmId); + private void play(Alarm alarm) { + // stop() checks to see if we are already playing. + stop(); - if (Log.LOGV) Log.v("AlarmKlaxon.play() " + mAlarmId + " alert " + mAlert); + if (Log.LOGV) { + Log.v("AlarmKlaxon.play() " + alarm.id + " alert " + alarm.alert); + } - // Fall back on the default alarm if the database does not have an - // alarm stored. - Uri alert = null; - boolean silent = false; - if (mAlert == null) { - alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); - if (Log.LOGV) { - Log.v("Using default alarm: " + alert.toString()); + if (!alarm.silent) { + Uri alert = alarm.alert; + // Fall back on the default alarm if the database does not have an + // alarm stored. + if (alert == null) { + alert = RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_ALARM); + if (Log.LOGV) { + Log.v("Using default alarm: " + alert.toString()); + } } - } else if (Alarms.ALARM_ALERT_SILENT.equals(mAlert)) { - silent = true; - } else { - alert = Uri.parse(mAlert); - } - if (!silent) { // TODO: Reuse mMediaPlayer instead of creating a new one and/or use // RingtoneManager. mMediaPlayer = new MediaPlayer(); @@ -141,17 +148,17 @@ class AlarmKlaxon implements Alarms.AlarmSettings { try { TelephonyManager tm = - (TelephonyManager) context.getSystemService( + (TelephonyManager) getSystemService( Context.TELEPHONY_SERVICE); // Check if we are in a call. If we are, use the in-call alarm // resource at a low volume to not disrupt the call. if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) { Log.v("Using the in-call alarm"); mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); - setDataSourceFromResource(context.getResources(), - mMediaPlayer, R.raw.in_call_alarm); + setDataSourceFromResource(getResources(), mMediaPlayer, + R.raw.in_call_alarm); } else { - mMediaPlayer.setDataSource(context, alert); + mMediaPlayer.setDataSource(this, alert); } startAlarm(mMediaPlayer); } catch (Exception ex) { @@ -161,8 +168,7 @@ class AlarmKlaxon implements Alarms.AlarmSettings { try { // Must reset the media player to clear the error state. mMediaPlayer.reset(); - setDataSourceFromResource(context.getResources(), - mMediaPlayer, + setDataSourceFromResource(getResources(), mMediaPlayer, com.android.internal.R.raw.fallbackring); startAlarm(mMediaPlayer); } catch (Exception ex2) { @@ -173,14 +179,15 @@ class AlarmKlaxon implements Alarms.AlarmSettings { } /* Start the vibrator after everything is ok with the media player */ - if (mVibrate) { + if (alarm.vibrate) { mVibrator.vibrate(sVibratePattern, 0); } else { mVibrator.cancel(); } - enableKiller(); + enableKiller(alarm); mPlaying = true; + mStartTime = System.currentTimeMillis(); } // Do the common stuff when starting the alarm. @@ -207,8 +214,8 @@ class AlarmKlaxon implements Alarms.AlarmSettings { * Stops alarm audio and disables alarm if it not snoozed and not * repeating */ - public void stop(Context context, boolean snoozed) { - if (Log.LOGV) Log.v("AlarmKlaxon.stop() " + mAlarmId); + public void stop() { + if (Log.LOGV) Log.v("AlarmKlaxon.stop()"); if (mPlaying) { mPlaying = false; @@ -221,23 +228,10 @@ class AlarmKlaxon implements Alarms.AlarmSettings { // Stop vibrator mVibrator.cancel(); - - /* disable alarm only if it is not set to repeat */ - if (!snoozed && ((mDaysOfWeek == null || !mDaysOfWeek.isRepeatSet()))) { - Alarms.enableAlarm(context, mAlarmId, false); - } } disableKiller(); } - /** - * This callback called when alarm killer times out unattended - * alarm - */ - public void setKillerCallback(KillerCallback killerCallback) { - mKillerCallback = killerCallback; - } - /** * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm * won't run all day. @@ -245,8 +239,8 @@ class AlarmKlaxon implements Alarms.AlarmSettings { * This just cancels the audio, but leaves the notification * popped, so the user will know that the alarm tripped. */ - private void enableKiller() { - mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER), + private void enableKiller(Alarm alarm) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER, alarm), 1000 * ALARM_TIMEOUT_SECONDS); } diff --git a/src/com/android/alarmclock/AlarmProvider.java b/src/com/android/alarmclock/AlarmProvider.java index 74fdd2e89..5849a384f 100644 --- a/src/com/android/alarmclock/AlarmProvider.java +++ b/src/com/android/alarmclock/AlarmProvider.java @@ -173,38 +173,38 @@ public class AlarmProvider extends ContentProvider { else values = new ContentValues(); - if (!values.containsKey(Alarms.AlarmColumns.HOUR)) - values.put(Alarms.AlarmColumns.HOUR, 0); + if (!values.containsKey(Alarm.Columns.HOUR)) + values.put(Alarm.Columns.HOUR, 0); - if (!values.containsKey(Alarms.AlarmColumns.MINUTES)) - values.put(Alarms.AlarmColumns.MINUTES, 0); + if (!values.containsKey(Alarm.Columns.MINUTES)) + values.put(Alarm.Columns.MINUTES, 0); - if (!values.containsKey(Alarms.AlarmColumns.DAYS_OF_WEEK)) - values.put(Alarms.AlarmColumns.DAYS_OF_WEEK, 0); + if (!values.containsKey(Alarm.Columns.DAYS_OF_WEEK)) + values.put(Alarm.Columns.DAYS_OF_WEEK, 0); - if (!values.containsKey(Alarms.AlarmColumns.ALARM_TIME)) - values.put(Alarms.AlarmColumns.ALARM_TIME, 0); + if (!values.containsKey(Alarm.Columns.ALARM_TIME)) + values.put(Alarm.Columns.ALARM_TIME, 0); - if (!values.containsKey(Alarms.AlarmColumns.ENABLED)) - values.put(Alarms.AlarmColumns.ENABLED, 0); + if (!values.containsKey(Alarm.Columns.ENABLED)) + values.put(Alarm.Columns.ENABLED, 0); - if (!values.containsKey(Alarms.AlarmColumns.VIBRATE)) - values.put(Alarms.AlarmColumns.VIBRATE, 1); + if (!values.containsKey(Alarm.Columns.VIBRATE)) + values.put(Alarm.Columns.VIBRATE, 1); - if (!values.containsKey(Alarms.AlarmColumns.MESSAGE)) - values.put(Alarms.AlarmColumns.MESSAGE, ""); + if (!values.containsKey(Alarm.Columns.MESSAGE)) + values.put(Alarm.Columns.MESSAGE, ""); - if (!values.containsKey(Alarms.AlarmColumns.ALERT)) - values.put(Alarms.AlarmColumns.ALERT, ""); + if (!values.containsKey(Alarm.Columns.ALERT)) + values.put(Alarm.Columns.ALERT, ""); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - long rowId = db.insert("alarms", Alarms.AlarmColumns.MESSAGE, values); + long rowId = db.insert("alarms", Alarm.Columns.MESSAGE, values); if (rowId < 0) { throw new SQLException("Failed to insert row into " + url); } if (Log.LOGV) Log.v("Added alarm rowId = " + rowId); - Uri newUrl = ContentUris.withAppendedId(Alarms.AlarmColumns.CONTENT_URI, rowId); + Uri newUrl = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, rowId); getContext().getContentResolver().notifyChange(newUrl, null); return newUrl; } diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index eed76b6ce..784428312 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -17,9 +17,15 @@ package com.android.alarmclock; import android.app.KeyguardManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.BroadcastReceiver; +import android.database.Cursor; +import android.os.Parcel; /** * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert @@ -33,28 +39,58 @@ public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - long now = System.currentTimeMillis(); - int id = intent.getIntExtra(Alarms.ID, 0); - long setFor = intent.getLongExtra(Alarms.TIME, 0); + // Take care of the easy intents first. + if (Alarms.CLEAR_NOTIFICATION.equals(intent.getAction())) { + // If this is the "Clear All Notifications" intent, stop the alarm + // service and return. + context.stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); + return; + } else if (Alarms.ALARM_KILLED.equals(intent.getAction())) { + // The alarm has been killed, update the notification + updateNotification(context, (Alarm) + intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA), + intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1)); + return; + } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) { + Alarms.saveSnoozeAlert(context, -1, -1); + return; + } + + Alarm alarm = null; + // Grab the alarm from the intent. Since the remote AlarmManagerService + // fills in the Intent to add some extra data, it must unparcel the + // Alarm object. It throws a ClassNotFoundException when unparcelling. + // To avoid this, do the marshalling ourselves. + final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA); + if (data != null) { + Parcel in = Parcel.obtain(); + in.unmarshall(data, 0, data.length); + in.setDataPosition(0); + alarm = Alarm.CREATOR.createFromParcel(in); + } + if (alarm == null) { + Log.v("AlarmReceiver failed to parse the alarm from the intent"); + return; + } + + long now = System.currentTimeMillis(); /* FIXME Intentionally verbose: always log this until we've fully debugged the app failing to start up */ - Log.v("AlarmReceiver.onReceive() id " + id + " setFor " + setFor + - " now " + now); + Log.v("AlarmReceiver.onReceive() id " + alarm.id + " setFor " + + alarm.time + " now " + now); - if (now > setFor + STALE_WINDOW * 1000) { - if (Log.LOGV) Log.v("AlarmReceiver ignoring stale alarm intent id" - + id + " setFor " + setFor + " now " + now); + if (now > alarm.time + STALE_WINDOW * 1000) { + if (Log.LOGV) { + Log.v("AlarmReceiver ignoring stale alarm intent id" + alarm.id + + " setFor " + alarm.time + " now " + now); + } return; } - /* Wake the device and stay awake until the AlarmAlert intent is - * handled. */ - AlarmAlertWakeLock.acquireCpuWakeLock(context); - /* Close dialogs and window shade */ - Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.sendBroadcast(i); + Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.sendBroadcast(closeDialogs); // Decide which activity to start based on the state of the keyguard. Class c = AlarmAlert.class; @@ -67,10 +103,94 @@ public class AlarmReceiver extends BroadcastReceiver { /* launch UI, explicitly stating that this is not due to user action * so that the current app's notification management is not disturbed */ - Intent fireAlarm = new Intent(context, c); - fireAlarm.putExtra(Alarms.ID, id); - fireAlarm.putExtra(Alarms.LABEL, intent.getStringExtra(Alarms.LABEL)); - fireAlarm.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION); - context.startActivity(fireAlarm); - } + Intent alarmAlert = new Intent(context, c); + alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); + alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_NO_USER_ACTION); + context.startActivity(alarmAlert); + + // Disable the snooze alert if this alarm is the snooze. + Alarms.disableSnoozeAlert(context, alarm.id); + // Disable this alarm if it does not repeat. + if (!alarm.daysOfWeek.isRepeatSet()) { + Alarms.enableAlarm(context, alarm.id, false); + } + // Enable the next alert if there is one. + Alarms.setNextAlert(context); + + // Play the alarm alert and vibrate the device. + Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION); + playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); + context.startService(playAlarm); + + // Trigger a notification that, when clicked, will show the alarm alert + // dialog. No need to check for fullscreen since this will always be + // launched from a user action. + Intent notify = new Intent(context, AlarmAlert.class); + notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); + PendingIntent pendingNotify = PendingIntent.getActivity(context, + alarm.id, notify, 0); + + // Use the alarm's label or the default label as the ticker text and + // main text of the notification. + String label = alarm.getLabelOrDefault(context); + Notification n = new Notification(R.drawable.stat_notify_alarm, + label, alarm.time); + n.setLatestEventInfo(context, label, + context.getString(R.string.alarm_notify_text), + pendingNotify); + n.flags |= Notification.FLAG_SHOW_LIGHTS; + n.ledARGB = 0xFF00FF00; + n.ledOnMS = 500; + n.ledOffMS = 500; + + // Set the deleteIntent for when the user clicks "Clear All + // Notifications" + Intent clearAll = new Intent(context, AlarmReceiver.class); + clearAll.setAction(Alarms.CLEAR_NOTIFICATION); + n.deleteIntent = PendingIntent.getBroadcast(context, 0, clearAll, 0); + + // Send the notification using the alarm id to easily identify the + // correct notification. + NotificationManager nm = getNotificationManager(context); + nm.notify(alarm.id, n); + + // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can + // pick it up. + AlarmAlertWakeLock.acquireCpuWakeLock(context); + } + + private NotificationManager getNotificationManager(Context context) { + return (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + } + + private void updateNotification(Context context, Alarm alarm, int timeout) { + NotificationManager nm = getNotificationManager(context); + + // If the alarm is null, just cancel the notification. + if (alarm == null) { + if (Log.LOGV) { + Log.v("Cannot update notification for killer callback"); + } + return; + } + + // Launch SetAlarm when clicked. + Intent viewAlarm = new Intent(context, SetAlarm.class); + viewAlarm.putExtra(Alarms.ALARM_ID, alarm.id); + PendingIntent intent = + PendingIntent.getActivity(context, alarm.id, viewAlarm, 0); + + // Update the notification to indicate that the alert has been + // silenced. + String label = alarm.getLabelOrDefault(context); + Notification n = new Notification(R.drawable.stat_notify_alarm, + label, alarm.time); + n.setLatestEventInfo(context, label, + context.getString(R.string.alarm_alert_alert_silenced, timeout), + intent); + n.flags |= Notification.FLAG_AUTO_CANCEL; + nm.notify(alarm.id, n); + } } diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index 40edf0b84..1745b9880 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -17,8 +17,6 @@ package com.android.alarmclock; import android.app.AlarmManager; -import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ContentResolver; import android.content.ContentValues; @@ -28,7 +26,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; -import android.provider.BaseColumns; +import android.os.Parcel; import android.provider.Settings; import android.text.format.DateFormat; @@ -40,354 +38,111 @@ import java.text.DateFormatSymbols; */ public class Alarms { - public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; - public final static String ID = "alarm_id"; - public final static String TIME = "alarm_time"; - public final static String LABEL = "alarm_label"; - public final static String ALARM_ALERT_SILENT = "silent"; + // This action triggers the AlarmReceiver as well as the AlarmKlaxon. It + // is a public action used in the manifest for receiving Alarm broadcasts + // from the alarm manager. + public static final String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; - final static String PREF_SNOOZE_ID = "snooze_id"; - final static String PREF_SNOOZE_TIME = "snooze_time"; - final static String PREF_SNOOZE_LABEL = "snooze_label"; - - private final static String DM12 = "E h:mm aa"; - private final static String DM24 = "E k:mm"; - - private final static String M12 = "h:mm aa"; - private final static String M24 = "k:mm"; - - /** - * Mapping from days in this application (where Monday is 0) to - * days in DateFormatSymbols (where Monday is 2). - */ - private static int[] DAY_MAP = new int[] { - Calendar.MONDAY, - Calendar.TUESDAY, - Calendar.WEDNESDAY, - Calendar.THURSDAY, - Calendar.FRIDAY, - Calendar.SATURDAY, - Calendar.SUNDAY, - }; - - static class DaysOfWeek { - - int mDays; - - /** - * Days of week coded as single int, convenient for DB - * storage: - * - * 0x00: no day - * 0x01: Monday - * 0x02: Tuesday - * 0x04: Wednesday - * 0x08: Thursday - * 0x10: Friday - * 0x20: Saturday - * 0x40: Sunday - */ - DaysOfWeek() { - this(0); - } - - DaysOfWeek(int days) { - mDays = days; - } - - public String toString(Context context, boolean showNever) { - StringBuilder ret = new StringBuilder(); - - /* no days */ - if (mDays == 0) return showNever ? context.getText( - R.string.never).toString() : ""; - - /* every day */ - if (mDays == 0x7f) { - return context.getText(R.string.every_day).toString(); - } - - /* count selected days */ - int dayCount = 0, days = mDays; - while (days > 0) { - if ((days & 1) == 1) dayCount++; - days >>= 1; - } - - /* short or long form? */ - DateFormatSymbols dfs = new DateFormatSymbols(); - String[] dayList = (dayCount > 1) ? - dfs.getShortWeekdays() : - dfs.getWeekdays(); - - /* selected days */ - for (int i = 0; i < 7; i++) { - if ((mDays & (1 << i)) != 0) { - ret.append(dayList[DAY_MAP[i]]); - dayCount -= 1; - if (dayCount > 0) ret.append( - context.getText(R.string.day_concat)); - } - } - return ret.toString(); - } - - /** - * @param day Mon=0 ... Sun=6 - * @return true if given day is set - */ - public boolean isSet(int day) { - return ((mDays & (1 << day)) > 0); - } - - public void set(int day, boolean set) { - if (set) { - mDays |= (1 << day); - } else { - mDays &= ~(1 << day); - } - } + // This is a private action used when the user clears all notifications. + public static final String CLEAR_NOTIFICATION = "clear_notification"; - public void set(DaysOfWeek dow) { - mDays = dow.mDays; - } + // This is a private action used by the AlarmKlaxon to update the UI to + // show the alarm has been killed. + public static final String ALARM_KILLED = "alarm_killed"; - public int getCoded() { - return mDays; - } + // Extra in the ALARM_KILLED intent to indicate to the user how long the + // alarm played before being killed. + public static final String ALARM_KILLED_TIMEOUT = "alarm_killed_timeout"; - public boolean equals(DaysOfWeek dow) { - return mDays == dow.mDays; - } + // This string is used to indicate a silent alarm in the db. + public static final String ALARM_ALERT_SILENT = "silent"; - // Returns days of week encoded in an array of booleans. - public boolean[] getBooleanArray() { - boolean[] ret = new boolean[7]; - for (int i = 0; i < 7; i++) { - ret[i] = isSet(i); - } - return ret; - } + // This intent is sent from the notification when the user cancels the + // snooze alert. + public static final String CANCEL_SNOOZE = "cancel_snooze"; - public void setCoded(int days) { - mDays = days; - } + // This string is used when passing an Alarm object through an intent. + public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm"; - /** - * @return true if alarm is set to repeat - */ - public boolean isRepeatSet() { - return mDays != 0; - } + // This extra is the raw Alarm object data. It is used in the + // AlarmManagerService to avoid a ClassNotFoundException when filling in + // the Intent extras. + public static final String ALARM_RAW_DATA = "intent.extra.alarm_raw"; - /** - * @return true if alarm is set to repeat every day - */ - public boolean isEveryDaySet() { - return mDays == 0x7f; - } + // This string is used to identify the alarm id passed to SetAlarm from the + // list of alarms. + public static final String ALARM_ID = "alarm_id"; + final static String PREF_SNOOZE_ID = "snooze_id"; + final static String PREF_SNOOZE_TIME = "snooze_time"; - /** - * returns number of days from today until next alarm - * @param c must be set to today - */ - public int getNextAlarm(Calendar c) { - if (mDays == 0) return -1; - int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7; - - int day, dayCount; - for (dayCount = 0; dayCount < 7; dayCount++) { - day = (today + dayCount) % 7; - if ((mDays & (1 << day)) > 0) { - break; - } - } - return dayCount; - } - } - - public static class AlarmColumns implements BaseColumns { - - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.android.alarmclock/alarm"); - - public static final String _ID = "_id"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = "_id ASC"; - - /** - * Hour in 24-hour localtime 0 - 23. - *

Type: INTEGER

- */ - public static final String HOUR = "hour"; - - /** - * Minutes in localtime 0 - 59 - *

Type: INTEGER

- */ - public static final String MINUTES = "minutes"; - - /** - * Days of week coded as integer - *

Type: INTEGER

- */ - public static final String DAYS_OF_WEEK = "daysofweek"; - - /** - * Alarm time in UTC milliseconds from the epoch. - *

Type: INTEGER

- */ - public static final String ALARM_TIME = "alarmtime"; - - /** - * True if alarm is active - *

Type: BOOLEAN

- */ - public static final String ENABLED = "enabled"; - - /** - * True if alarm should vibrate - *

Type: BOOLEAN

- */ - public static final String VIBRATE = "vibrate"; - - /** - * Message to show when alarm triggers - * Note: not currently used - *

Type: STRING

- */ - public static final String MESSAGE = "message"; - - /** - * Audio alert to play when alarm triggers - *

Type: STRING

- */ - public static final String ALERT = "alert"; - - static final String[] ALARM_QUERY_COLUMNS = { - _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME, - ENABLED, VIBRATE, MESSAGE, ALERT}; - - /** - * These save calls to cursor.getColumnIndexOrThrow() - * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS - */ - public static final int ALARM_ID_INDEX = 0; - public static final int ALARM_HOUR_INDEX = 1; - public static final int ALARM_MINUTES_INDEX = 2; - public static final int ALARM_DAYS_OF_WEEK_INDEX = 3; - public static final int ALARM_TIME_INDEX = 4; - public static final int ALARM_ENABLED_INDEX = 5; - public static final int ALARM_VIBRATE_INDEX = 6; - public static final int ALARM_MESSAGE_INDEX = 7; - public static final int ALARM_ALERT_INDEX = 8; - } + private final static String DM12 = "E h:mm aa"; + private final static String DM24 = "E k:mm"; - /** - * getAlarm and getAlarms call this interface to report alarms in - * the database - */ - static interface AlarmSettings { - void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert); - } + private final static String M12 = "h:mm aa"; + private final static String M24 = "k:mm"; /** * Creates a new Alarm. */ - public synchronized static Uri addAlarm(ContentResolver contentResolver) { + public static Uri addAlarm(ContentResolver contentResolver) { ContentValues values = new ContentValues(); - values.put(Alarms.AlarmColumns.HOUR, 8); - return contentResolver.insert(AlarmColumns.CONTENT_URI, values); + values.put(Alarm.Columns.HOUR, 8); + return contentResolver.insert(Alarm.Columns.CONTENT_URI, values); } /** * Removes an existing Alarm. If this alarm is snoozing, disables * snooze. Sets next alert. */ - public synchronized static void deleteAlarm( + public static void deleteAlarm( Context context, int alarmId) { ContentResolver contentResolver = context.getContentResolver(); /* If alarm is snoozing, lose it */ - int snoozeId = getSnoozeAlarmId(context); - if (snoozeId == alarmId) disableSnoozeAlert(context); + disableSnoozeAlert(context, alarmId); - Uri uri = ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId); - deleteAlarm(contentResolver, uri); + Uri uri = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId); + contentResolver.delete(uri, "", null); setNextAlert(context); } - private synchronized static void deleteAlarm( - ContentResolver contentResolver, Uri uri) { - contentResolver.delete(uri, "", null); - } - /** * Queries all alarms * @return cursor over all alarms */ - public synchronized static Cursor getAlarmsCursor( - ContentResolver contentResolver) { + public static Cursor getAlarmsCursor(ContentResolver contentResolver) { return contentResolver.query( - AlarmColumns.CONTENT_URI, AlarmColumns.ALARM_QUERY_COLUMNS, - null, null, AlarmColumns.DEFAULT_SORT_ORDER); + Alarm.Columns.CONTENT_URI, Alarm.Columns.ALARM_QUERY_COLUMNS, + null, null, Alarm.Columns.DEFAULT_SORT_ORDER); } - /** - * Calls the AlarmSettings.reportAlarm interface on all alarms found in db. - */ - public synchronized static void getAlarms( - ContentResolver contentResolver, AlarmSettings alarmSettings) { - Cursor cursor = getAlarmsCursor(contentResolver); - getAlarms(alarmSettings, cursor); - cursor.close(); - } - - private synchronized static void getAlarms( - AlarmSettings alarmSettings, Cursor cur) { - if (cur.moveToFirst()) { - do { - // Get the field values - int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX); - int hour = cur.getInt(AlarmColumns.ALARM_HOUR_INDEX); - int minutes = cur.getInt(AlarmColumns.ALARM_MINUTES_INDEX); - int daysOfWeek = cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX); - boolean enabled = cur.getInt(AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false; - boolean vibrate = cur.getInt(AlarmColumns.ALARM_VIBRATE_INDEX) == 1 ? true : false; - String message = cur.getString(AlarmColumns.ALARM_MESSAGE_INDEX); - String alert = cur.getString(AlarmColumns.ALARM_ALERT_INDEX); - alarmSettings.reportAlarm( - id, enabled, hour, minutes, new DaysOfWeek(daysOfWeek), - vibrate, message, alert); - } while (cur.moveToNext()); - } + // Private method to get a more limited set of alarms from the database. + private static Cursor getFilteredAlarmsCursor( + ContentResolver contentResolver) { + return contentResolver.query(Alarm.Columns.CONTENT_URI, + Alarm.Columns.ALARM_QUERY_COLUMNS, Alarm.Columns.WHERE_ENABLED, + null, null); } /** - * Calls the AlarmSettings.reportAlarm interface on alarm with given - * alarmId + * Return an Alarm object representing the alarm id in the database. + * Returns null if no alarm exists. */ - public synchronized static void getAlarm( - ContentResolver contentResolver, AlarmSettings alarmSetting, - int alarmId) { + public static Alarm getAlarm(ContentResolver contentResolver, int alarmId) { Cursor cursor = contentResolver.query( - ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, alarmId), - AlarmColumns.ALARM_QUERY_COLUMNS, - null, null, AlarmColumns.DEFAULT_SORT_ORDER); - - getAlarms(alarmSetting, cursor); - cursor.close(); + ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId), + Alarm.Columns.ALARM_QUERY_COLUMNS, + null, null, null); + Alarm alarm = null; + if (cursor != null) { + if (cursor.moveToFirst()) { + alarm = new Alarm(cursor); + } + cursor.close(); + } + return alarm; } @@ -405,32 +160,35 @@ public class Alarms { * @param message corresponds to the MESSAGE column * @param alert corresponds to the ALERT column */ - public synchronized static void setAlarm( + public static void setAlarm( Context context, int id, boolean enabled, int hour, int minutes, - DaysOfWeek daysOfWeek, boolean vibrate, String message, + Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message, String alert) { ContentValues values = new ContentValues(8); ContentResolver resolver = context.getContentResolver(); - long time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis(); + // Set the alarm_time value if this alarm does not repeat. This will be + // used later to disable expired alarms. + long time = 0; + if (!daysOfWeek.isRepeatSet()) { + time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis(); + } if (Log.LOGV) Log.v( "** setAlarm * idx " + id + " hour " + hour + " minutes " + minutes + " enabled " + enabled + " time " + time); - values.put(AlarmColumns.ENABLED, enabled ? 1 : 0); - values.put(AlarmColumns.HOUR, hour); - values.put(AlarmColumns.MINUTES, minutes); - values.put(AlarmColumns.ALARM_TIME, time); - values.put(AlarmColumns.DAYS_OF_WEEK, daysOfWeek.getCoded()); - values.put(AlarmColumns.VIBRATE, vibrate); - values.put(AlarmColumns.MESSAGE, message); - values.put(AlarmColumns.ALERT, alert); - resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id), + values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0); + values.put(Alarm.Columns.HOUR, hour); + values.put(Alarm.Columns.MINUTES, minutes); + values.put(Alarm.Columns.ALARM_TIME, time); + values.put(Alarm.Columns.DAYS_OF_WEEK, daysOfWeek.getCoded()); + values.put(Alarm.Columns.VIBRATE, vibrate); + values.put(Alarm.Columns.MESSAGE, message); + values.put(Alarm.Columns.ALERT, alert); + resolver.update(ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, id), values, null, null); - int aid = disableSnoozeAlert(context); - if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false); setNextAlert(context); } @@ -441,103 +199,63 @@ public class Alarms { * @param enabled corresponds to the ENABLED column */ - public synchronized static void enableAlarm( + public static void enableAlarm( final Context context, final int id, boolean enabled) { - int aid = disableSnoozeAlert(context); - if (aid != -1 && aid != id) enableAlarmInternal(context, aid, false); enableAlarmInternal(context, id, enabled); setNextAlert(context); } - private synchronized static void enableAlarmInternal( - final Context context, final int id, boolean enabled) { - ContentResolver resolver = context.getContentResolver(); + private static void enableAlarmInternal(final Context context, + final int id, boolean enabled) { + enableAlarmInternal(context, getAlarm(context.getContentResolver(), id), + enabled); + } - class EnableAlarm implements AlarmSettings { - public int mHour; - public int mMinutes; - public DaysOfWeek mDaysOfWeek; - - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - mHour = hour; - mMinutes = minutes; - mDaysOfWeek = daysOfWeek; - } - } + private static void enableAlarmInternal(final Context context, + final Alarm alarm, boolean enabled) { + ContentResolver resolver = context.getContentResolver(); ContentValues values = new ContentValues(2); - values.put(AlarmColumns.ENABLED, enabled ? 1 : 0); + values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0); - /* If we are enabling the alarm, load hour/minutes/daysOfWeek - from db, so we can calculate alarm time */ + // If we are enabling the alarm, calculate alarm time since the time + // value in Alarm may be old. if (enabled) { - EnableAlarm enableAlarm = new EnableAlarm(); - getAlarm(resolver, enableAlarm, id); - if (enableAlarm.mDaysOfWeek == null) { - /* Under monkey, sometimes reportAlarm is never - called */ - Log.e("** enableAlarmInternal failed " + id + " h " + - enableAlarm.mHour + " m " + enableAlarm.mMinutes); - return; + long time = 0; + if (!alarm.daysOfWeek.isRepeatSet()) { + time = calculateAlarm(alarm.hour, alarm.minutes, + alarm.daysOfWeek).getTimeInMillis(); } - - long time = calculateAlarm(enableAlarm.mHour, enableAlarm.mMinutes, - enableAlarm.mDaysOfWeek).getTimeInMillis(); - values.put(AlarmColumns.ALARM_TIME, time); + values.put(Alarm.Columns.ALARM_TIME, time); } - resolver.update(ContentUris.withAppendedId(AlarmColumns.CONTENT_URI, id), - values, null, null); + resolver.update(ContentUris.withAppendedId( + Alarm.Columns.CONTENT_URI, alarm.id), values, null, null); } - - /** - * Calculates next scheduled alert - */ - static class AlarmCalculator implements AlarmSettings { - private long mMinAlert = Long.MAX_VALUE; - private int mMinIdx = -1; - private String mLabel; - - /** - * returns next scheduled alert, MAX_VALUE if none - */ - public long getAlert() { - return mMinAlert; - } - public int getIndex() { - return mMinIdx; - } - public String getLabel() { - return mLabel; - } - - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - if (enabled) { - long atTime = calculateAlarm(hour, minutes, - daysOfWeek).getTimeInMillis(); - /* Log.i("** SET ALERT* idx " + idx + " hour " + hour + " minutes " + - minutes + " enabled " + enabled + " calc " + atTime); */ - if (atTime < mMinAlert) { - mMinIdx = idx; - mMinAlert = atTime; - mLabel = message; - } + public static Alarm calculateNextAlert(final Context context) { + Alarm alarm = null; + long minTime = Long.MAX_VALUE; + Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver()); + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + Alarm a = new Alarm(cursor); + // A time of 0 indicates this is a repeating alarm, so + // calculate the time to get the next alert. + if (a.time == 0) { + a.time = calculateAlarm(a.hour, a.minutes, a.daysOfWeek) + .getTimeInMillis(); + } + if (a.time < minTime) { + minTime = a.time; + alarm = a; + } + } while (cursor.moveToNext()); } + cursor.close(); } - } - - static AlarmCalculator calculateNextAlert(final Context context) { - ContentResolver resolver = context.getContentResolver(); - AlarmCalculator alarmCalc = new AlarmCalculator(); - getAlarms(resolver, alarmCalc); - return alarmCalc; + return alarm; } /** @@ -545,54 +263,39 @@ public class Alarms { * boot. */ public static void disableExpiredAlarms(final Context context) { - Cursor cur = getAlarmsCursor(context.getContentResolver()); + Cursor cur = getFilteredAlarmsCursor(context.getContentResolver()); long now = System.currentTimeMillis(); if (cur.moveToFirst()) { do { - // Get the field values - int id = cur.getInt(AlarmColumns.ALARM_ID_INDEX); - boolean enabled = cur.getInt( - AlarmColumns.ALARM_ENABLED_INDEX) == 1 ? true : false; - DaysOfWeek daysOfWeek = new DaysOfWeek( - cur.getInt(AlarmColumns.ALARM_DAYS_OF_WEEK_INDEX)); - long time = cur.getLong(AlarmColumns.ALARM_TIME_INDEX); - - if (enabled && !daysOfWeek.isRepeatSet() && time < now) { - if (Log.LOGV) Log.v( - "** DISABLE " + id + " now " + now +" set " + time); - enableAlarmInternal(context, id, false); + Alarm alarm = new Alarm(cur); + // A time of 0 means this alarm repeats. If the time is + // non-zero, check if the time is before now. + if (alarm.time != 0 && alarm.time < now) { + if (Log.LOGV) { + Log.v("** DISABLE " + alarm.id + " now " + now +" set " + + alarm.time); + } + enableAlarmInternal(context, alarm, false); } } while (cur.moveToNext()); } cur.close(); } - private static NotificationManager getNotificationManager( - final Context context) { - return (NotificationManager) context.getSystemService( - context.NOTIFICATION_SERVICE); - } - /** * Called at system startup, on time/timezone change, and whenever * the user changes alarm settings. Activates snooze if set, * otherwise loads all alarms, activates next alert. */ public static void setNextAlert(final Context context) { - int snoozeId = getSnoozeAlarmId(context); - if (snoozeId == -1) { - AlarmCalculator ac = calculateNextAlert(context); - int id = ac.getIndex(); - long atTime = ac.getAlert(); - - if (atTime < Long.MAX_VALUE) { - enableAlert(context, id, ac.getLabel(), atTime); + if (!enableSnoozeAlert(context)) { + Alarm alarm = calculateNextAlert(context); + if (alarm != null) { + enableAlert(context, alarm, alarm.time); } else { - disableAlert(context, id); + disableAlert(context); } - } else { - enableSnoozeAlert(context); } } @@ -600,37 +303,38 @@ public class Alarms { * Sets alert in AlarmManger and StatusBar. This is what will * actually launch the alert when the alarm triggers. * - * Note: In general, apps should call setNextAlert() instead of - * this method. setAlert() is only used outside this class when - * the alert is not to be driven by the state of the db. "Snooze" - * uses this API, as we do not want to alter the alarm in the db - * with each snooze. - * - * @param id Alarm ID. + * @param alarm Alarm. * @param atTimeInMillis milliseconds since epoch */ - static void enableAlert(Context context, int id, String label, - long atTimeInMillis) { + private static void enableAlert(Context context, final Alarm alarm, + final long atTimeInMillis) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(ALARM_ALERT_ACTION); - if (Log.LOGV) Log.v("** setAlert id " + id + " atTime " + atTimeInMillis); - intent.putExtra(ID, id); - if (label != null) { - intent.putExtra(LABEL, label); + if (Log.LOGV) { + Log.v("** setAlert id " + alarm.id + " atTime " + atTimeInMillis); } - intent.putExtra(TIME, atTimeInMillis); + + Intent intent = new Intent(ALARM_ALERT_ACTION); + + // XXX: This is a slight hack to avoid an exception in the remote + // AlarmManagerService process. The AlarmManager adds extra data to + // this Intent which causes it to inflate. Since the remote process + // does not know about the Alarm class, it throws a + // ClassNotFoundException. + // + // To avoid this, we marshall the data ourselves and then parcel a plain + // byte[] array. The AlarmReceiver class knows to build the Alarm + // object from the byte[] array. + Parcel out = Parcel.obtain(); + alarm.writeToParcel(out, 0); + out.setDataPosition(0); + intent.putExtra(ALARM_RAW_DATA, out.marshall()); + PendingIntent sender = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - if (true) { - am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); - } else { - // a five-second alarm, for testing - am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, - sender); - } + am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); setStatusBarIcon(context, true); @@ -645,48 +349,49 @@ public class Alarms { * * @param id Alarm ID. */ - static void disableAlert(Context context, int id) { + static void disableAlert(Context context) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(ALARM_ALERT_ACTION); - intent.putExtra(ID, id); PendingIntent sender = PendingIntent.getBroadcast( - context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + context, 0, new Intent(ALARM_ALERT_ACTION), + PendingIntent.FLAG_CANCEL_CURRENT); am.cancel(sender); setStatusBarIcon(context, false); saveNextAlarm(context, ""); } - static void saveSnoozeAlert(final Context context, int id, - long atTimeInMillis, String label) { + static void saveSnoozeAlert(final Context context, final int id, + final long time) { SharedPreferences prefs = context.getSharedPreferences( AlarmClock.PREFERENCES, 0); SharedPreferences.Editor ed = prefs.edit(); - ed.putInt(PREF_SNOOZE_ID, id); - ed.putLong(PREF_SNOOZE_TIME, atTimeInMillis); - if (label != null) { - ed.putString(PREF_SNOOZE_LABEL, label); + if (id == -1) { + ed.clear(); + } else { + ed.putInt(PREF_SNOOZE_ID, id); + ed.putLong(PREF_SNOOZE_TIME, time); } ed.commit(); + // Set the next alert after updating the snooze. + setNextAlert(context); } /** - * @return ID of alarm disabled, if disabled, -1 otherwise - */ - static int disableSnoozeAlert(final Context context) { - int id = getSnoozeAlarmId(context); - if (id == -1) return -1; - saveSnoozeAlert(context, -1, 0, null); - return id; - } - - /** - * @return alarm ID of snoozing alarm, -1 if snooze unset + * Disable the snooze alert if the given id matches the snooze id. */ - private static int getSnoozeAlarmId(final Context context) { + static void disableSnoozeAlert(final Context context, final int id) { SharedPreferences prefs = context.getSharedPreferences( AlarmClock.PREFERENCES, 0); - return prefs.getInt(PREF_SNOOZE_ID, -1); + int snoozeId = prefs.getInt(PREF_SNOOZE_ID, -1); + if (snoozeId == -1) { + // No snooze set, do nothing. + return; + } else if (snoozeId == id) { + // This is the same id so clear the shared prefs. + SharedPreferences.Editor ed = prefs.edit(); + ed.clear(); + ed.commit(); + } } /** @@ -698,16 +403,22 @@ public class Alarms { AlarmClock.PREFERENCES, 0); int id = prefs.getInt(PREF_SNOOZE_ID, -1); - if (id == -1) return false; - long atTimeInMillis = prefs.getLong(PREF_SNOOZE_TIME, -1); - if (id == -1) return false; - // Try to get the label from the snooze preference. - String label = prefs.getString(PREF_SNOOZE_LABEL, null); - enableAlert(context, id, label, atTimeInMillis); + if (id == -1) { + return false; + } + long time = prefs.getLong(PREF_SNOOZE_TIME, -1); + + // Get the alarm from the db. + final Alarm alarm = getAlarm(context.getContentResolver(), id); + // The time in the database is either 0 (repeating) or a specific time + // for a non-repeating alarm. Update this value so the AlarmReceiver + // has the right time to compare. + alarm.time = time; + + enableAlert(context, alarm, time); return true; } - /** * Tells the StatusBar whether the alarm is enabled or disabled */ @@ -724,7 +435,7 @@ public class Alarms { * @param minute 0-59 * @param daysOfWeek 0-59 */ - static Calendar calculateAlarm(int hour, int minute, DaysOfWeek daysOfWeek) { + static Calendar calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek) { // start with now Calendar c = Calendar.getInstance(); @@ -752,7 +463,7 @@ public class Alarms { } static String formatTime(final Context context, int hour, int minute, - DaysOfWeek daysOfWeek) { + Alarm.DaysOfWeek daysOfWeek) { Calendar c = calculateAlarm(hour, minute, daysOfWeek); return formatTime(context, c); } diff --git a/src/com/android/alarmclock/RepeatPreference.java b/src/com/android/alarmclock/RepeatPreference.java index 7111ec6d2..6af023ba4 100644 --- a/src/com/android/alarmclock/RepeatPreference.java +++ b/src/com/android/alarmclock/RepeatPreference.java @@ -28,10 +28,10 @@ import java.util.Calendar; public class RepeatPreference extends ListPreference { // Initial value that can be set with the values saved in the database. - private Alarms.DaysOfWeek mDaysOfWeek = new Alarms.DaysOfWeek(); + private Alarm.DaysOfWeek mDaysOfWeek = new Alarm.DaysOfWeek(0); // New value that will be set if a positive result comes back from the // dialog. - private Alarms.DaysOfWeek mNewDaysOfWeek = new Alarms.DaysOfWeek(); + private Alarm.DaysOfWeek mNewDaysOfWeek = new Alarm.DaysOfWeek(0); public RepeatPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -73,13 +73,13 @@ public class RepeatPreference extends ListPreference { }); } - public void setDaysOfWeek(Alarms.DaysOfWeek dow) { + public void setDaysOfWeek(Alarm.DaysOfWeek dow) { mDaysOfWeek.set(dow); mNewDaysOfWeek.set(dow); setSummary(dow.toString(getContext(), true)); } - public Alarms.DaysOfWeek getDaysOfWeek() { + public Alarm.DaysOfWeek getDaysOfWeek() { return mDaysOfWeek; } } diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index bf783fb4b..7c3664919 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -44,7 +44,7 @@ import android.widget.Toast; * Manages each alarm */ public class SetAlarm extends PreferenceActivity - implements Alarms.AlarmSettings, TimePickerDialog.OnTimeSetListener { + implements TimePickerDialog.OnTimeSetListener { private EditTextPreference mLabel; private Preference mTimePref; @@ -58,11 +58,9 @@ public class SetAlarm extends PreferenceActivity private int mHour; private int mMinutes; - private boolean mReportAlarmCalled; - /** - * Set an alarm. Requires an Alarms.ID to be passed in as an - * extra + * Set an alarm. Requires an Alarms.ALARM_ID to be passed in as an + * extra. FIXME: Pass an Alarm object like every other Activity. */ @Override protected void onCreate(Bundle icicle) { @@ -87,21 +85,22 @@ public class SetAlarm extends PreferenceActivity mRepeatPref = (RepeatPreference) findPreference("setRepeat"); Intent i = getIntent(); - mId = i.getIntExtra(Alarms.ID, -1); + mId = i.getIntExtra(Alarms.ALARM_ID, -1); if (Log.LOGV) { Log.v("In SetAlarm, alarm id = " + mId); } - mReportAlarmCalled = false; /* load alarm details from database */ - Alarms.getAlarm(getContentResolver(), this, mId); - /* This should never happen, but does occasionally with the monkey. - * I believe it's a race condition where a deleted alarm is opened - * before the alarm list is refreshed. */ - if (!mReportAlarmCalled) { - Log.e("reportAlarm never called!"); - finish(); - } + Alarm alarm = Alarms.getAlarm(getContentResolver(), mId); + mLabel.setText(alarm.label); + mLabel.setSummary(alarm.label); + mHour = alarm.hour; + mMinutes = alarm.minutes; + mRepeatPref.setDaysOfWeek(alarm.daysOfWeek); + mVibratePref.setChecked(alarm.vibrate); + // Give the alert uri to the preference. + mAlarmPref.setAlert(alarm.alert); + updateTime(); // We have to do this to get the save/cancel buttons to highlight on // their own. @@ -168,52 +167,6 @@ public class SetAlarm extends PreferenceActivity updateTime(); } - /** - * Alarms.AlarmSettings implementation. Database feeds current - * settings in through this call - */ - public void reportAlarm( - int idx, boolean enabled, int hour, int minutes, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String label, - String alert) { - - mLabel.setText(label); - mLabel.setSummary(label); - mHour = hour; - mMinutes = minutes; - mRepeatPref.setDaysOfWeek(daysOfWeek); - mVibratePref.setChecked(vibrate); - - Uri alertUri = null; - if (Alarms.ALARM_ALERT_SILENT.equals(alert)) { - if (Log.LOGV) { - Log.v("reportAlarm: silent alert"); - } - } else { - if (alert != null && alert.length() != 0) { - alertUri = Uri.parse(alert); - } - - // If the database alert is null or it failed to parse, use the - // default alert. - if (alertUri == null) { - alertUri = RingtoneManager.getDefaultUri( - RingtoneManager.TYPE_ALARM); - } - - if (Log.LOGV) { - Log.v("reportAlarm alert: " + alert + " uri: " + alertUri); - } - } - - // Give the alert uri to the preference. - mAlarmPref.setAlert(alertUri); - - updateTime(); - - mReportAlarmCalled = true; - } - private void updateTime() { if (Log.LOGV) { Log.v("updateTime " + mId); @@ -237,7 +190,7 @@ public class SetAlarm extends PreferenceActivity */ private static void saveAlarm( Context context, int id, boolean enabled, int hour, int minute, - Alarms.DaysOfWeek daysOfWeek, boolean vibrate, String label, + Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String label, String alert, boolean popToast) { if (Log.LOGV) Log.v("** saveAlarm " + id + " " + label + " " + enabled + " " + hour + " " + minute + " vibe " + vibrate); @@ -256,7 +209,7 @@ public class SetAlarm extends PreferenceActivity * goes off. This helps prevent "am/pm" mistakes. */ static void popAlarmSetToast(Context context, int hour, int minute, - Alarms.DaysOfWeek daysOfWeek) { + Alarm.DaysOfWeek daysOfWeek) { String toastText = formatToast(context, hour, minute, daysOfWeek); Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); @@ -269,7 +222,7 @@ public class SetAlarm extends PreferenceActivity * now" */ static String formatToast(Context context, int hour, int minute, - Alarms.DaysOfWeek daysOfWeek) { + Alarm.DaysOfWeek daysOfWeek) { long alarm = Alarms.calculateAlarm(hour, minute, daysOfWeek).getTimeInMillis(); long delta = alarm - System.currentTimeMillis();; -- cgit v1.2.3 From 89811e14c23c7fe6f88a21d13429613b756bce3f Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Wed, 8 Jul 2009 10:58:06 -0400 Subject: Add a setting to change the behavior of the side buttons during an alarm. The default behavior is to dismiss the alarm but the setting allows for snoozing the alarm or to do nothing. Also changed some of the strings to be more consistent. --- src/com/android/alarmclock/AlarmAlert.java | 26 ++++++++++++++-- src/com/android/alarmclock/SettingsActivity.java | 39 ++++++++++++------------ 2 files changed, 43 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 3567d87ac..038521b98 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -46,8 +46,12 @@ import java.util.Calendar; */ public class AlarmAlert extends Activity { + // These defaults must match the values in res/xml/settings.xml private static final String DEFAULT_SNOOZE = "10"; + private static final String DEFAULT_VOLUME_BEHAVIOR = "2"; + private Alarm mAlarm; + private int mVolumeBehavior; // Receives the ALARM_KILLED action from the AlarmKlaxon. private BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -66,6 +70,13 @@ public class AlarmAlert extends Activity { mAlarm = getIntent().getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); + // Get the volume/camera button behavior setting + final String vol = + PreferenceManager.getDefaultSharedPreferences(this) + .getString(SettingsActivity.KEY_VOLUME_BEHAVIOR, + DEFAULT_VOLUME_BEHAVIOR); + mVolumeBehavior = Integer.parseInt(vol); + requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); updateLayout(); @@ -131,7 +142,7 @@ public class AlarmAlert extends Activity { private void snooze() { final String snooze = PreferenceManager.getDefaultSharedPreferences(this) - .getString("snooze_duration", DEFAULT_SNOOZE); + .getString(SettingsActivity.KEY_ALARM_SNOOZE, DEFAULT_SNOOZE); int snoozeMinutes = Integer.parseInt(snooze); final long snoozeTime = System.currentTimeMillis() @@ -240,7 +251,18 @@ public class AlarmAlert extends Activity { case KeyEvent.KEYCODE_CAMERA: case KeyEvent.KEYCODE_FOCUS: if (up) { - dismiss(false); + switch (mVolumeBehavior) { + case 1: + snooze(); + break; + + case 2: + dismiss(false); + break; + + default: + break; + } } return true; default: diff --git a/src/com/android/alarmclock/SettingsActivity.java b/src/com/android/alarmclock/SettingsActivity.java index 59bf04022..f0b97fd4f 100644 --- a/src/com/android/alarmclock/SettingsActivity.java +++ b/src/com/android/alarmclock/SettingsActivity.java @@ -36,23 +36,15 @@ public class SettingsActivity extends PreferenceActivity private static final String KEY_ALARM_IN_SILENT_MODE = "alarm_in_silent_mode"; - private static final String KEY_ALARM_SNOOZE = + static final String KEY_ALARM_SNOOZE = "snooze_duration"; - private CheckBoxPreference mAlarmInSilentModePref; + static final String KEY_VOLUME_BEHAVIOR = + "volume_button_setting"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.settings); - - mAlarmInSilentModePref = - (CheckBoxPreference) findPreference(KEY_ALARM_IN_SILENT_MODE); - - final ListPreference snooze = - (ListPreference) findPreference(KEY_ALARM_SNOOZE); - snooze.setSummary(snooze.getEntry()); - snooze.setOnPreferenceChangeListener(this); } @Override @@ -64,14 +56,13 @@ public class SettingsActivity extends PreferenceActivity @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - - if (preference == mAlarmInSilentModePref) { - + if (KEY_ALARM_IN_SILENT_MODE.equals(preference.getKey())) { + CheckBoxPreference pref = (CheckBoxPreference) preference; int ringerModeStreamTypes = Settings.System.getInt( getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - if (mAlarmInSilentModePref.isChecked()) { + if (pref.isChecked()) { ringerModeStreamTypes &= ~ALARM_STREAM_TYPE_BIT; } else { ringerModeStreamTypes |= ALARM_STREAM_TYPE_BIT; @@ -88,17 +79,25 @@ public class SettingsActivity extends PreferenceActivity } public boolean onPreferenceChange(Preference pref, Object newValue) { - ListPreference listPref = (ListPreference) pref; - int idx = listPref.findIndexOfValue((String) newValue); + final ListPreference listPref = (ListPreference) pref; + final int idx = listPref.findIndexOfValue((String) newValue); listPref.setSummary(listPref.getEntries()[idx]); return true; } private void refresh() { - int silentModeStreams = Settings.System.getInt(getContentResolver(), - Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - mAlarmInSilentModePref.setChecked( + final CheckBoxPreference alarmInSilentModePref = + (CheckBoxPreference) findPreference(KEY_ALARM_IN_SILENT_MODE); + final int silentModeStreams = + Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + alarmInSilentModePref.setChecked( (silentModeStreams & ALARM_STREAM_TYPE_BIT) == 0); + + final ListPreference snooze = + (ListPreference) findPreference(KEY_ALARM_SNOOZE); + snooze.setSummary(snooze.getEntry()); + snooze.setOnPreferenceChangeListener(this); } } -- cgit v1.2.3 From 439d5bd5995ad6d9c0e750678a7c2176e2e1d67b Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 14 Jul 2009 08:50:14 -0400 Subject: Change the time format to show 2 digits for 24-hour mode. This will show 09 instead of 9 for all hours before 10 am. --- src/com/android/alarmclock/Alarms.java | 13 +++++++------ src/com/android/alarmclock/DigitalClock.java | 3 +-- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index e34b1b56c..8e718366a 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -40,20 +40,21 @@ import java.text.DateFormatSymbols; */ public class Alarms { - public final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; - public final static String ID = "alarm_id"; - public final static String TIME = "alarm_time"; - public final static String LABEL = "alarm_label"; + final static String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; + final static String ID = "alarm_id"; + final static String TIME = "alarm_time"; + final static String LABEL = "alarm_label"; final static String PREF_SNOOZE_ID = "snooze_id"; final static String PREF_SNOOZE_TIME = "snooze_time"; final static String PREF_SNOOZE_LABEL = "snooze_label"; private final static String DM12 = "E h:mm aa"; - private final static String DM24 = "E k:mm"; + private final static String DM24 = "E kk:mm"; private final static String M12 = "h:mm aa"; - private final static String M24 = "k:mm"; + // Shared with DigitalClock + final static String M24 = "kk:mm"; /** * Mapping from days in this application (where Monday is 0) to diff --git a/src/com/android/alarmclock/DigitalClock.java b/src/com/android/alarmclock/DigitalClock.java index d131066e1..7ae92f185 100644 --- a/src/com/android/alarmclock/DigitalClock.java +++ b/src/com/android/alarmclock/DigitalClock.java @@ -40,7 +40,6 @@ import java.util.Calendar; public class DigitalClock extends LinearLayout { private final static String M12 = "h:mm"; - private final static String M24 = "k:mm"; private Calendar mCalendar; private String mFormat; @@ -188,7 +187,7 @@ public class DigitalClock extends LinearLayout { } private void setDateFormat() { - mFormat = Alarms.get24HourMode(mContext) ? M24 : M12; + mFormat = Alarms.get24HourMode(mContext) ? Alarms.M24 : M12; mAmPm.setShowAmPm(mFormat == M12); } -- cgit v1.2.3 From 1bc128fd4266656ebc7e8abc6bf22652f6a6134a Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Tue, 21 Jul 2009 16:28:30 -0700 Subject: Make the "This alarm is set for N hours from now" message more localizable. It was concatenating the days/minutes/seconds segments together with an "and" string, which works OK in English but not so well in other languages. Bug 1973471 --- src/com/android/alarmclock/SetAlarm.java | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index 807b469df..c14181263 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -347,26 +347,12 @@ public class SetAlarm extends PreferenceActivity boolean dispHour = hours > 0; boolean dispMinute = minutes > 0; - String ret; - if (!(dispDays || dispHour || dispMinute)) { - ret = context.getString(R.string.subminute); - } else { - String parts[] = new String[5]; - parts[0] = daySeq; - parts[1] = !dispDays ? "" : - dispHour && dispMinute ? context.getString(R.string.space) : - !dispHour && !dispMinute ? "" : - context.getString(R.string.and); - parts[2] = dispHour ? hourSeq : ""; - parts[3] = dispHour && dispMinute ? context.getString(R.string.and) : ""; - parts[4] = dispMinute ? minSeq : ""; - ret = context.getString(R.string.combiner, (Object[])parts); - } + int index = (dispDays ? 1 : 0) | + (dispHour ? 2 : 0) | + (dispMinute ? 4 : 0); - ret = context.getString(R.string.alarm_set, ret); - /* if (Log.LOGV) Log.v("** TOAST daySeq " + daySeq + " hourSeq " + hourSeq + - " minSeq " + minSeq + " ret " + ret); */ - return ret; + String[] formats = context.getResources().getStringArray(R.array.alarm_set); + return String.format(formats[index], daySeq, hourSeq, minSeq); } public boolean onCreateOptionsMenu(Menu menu) { -- cgit v1.2.3 From e77fa5af5ac139f69ad504dbadd6f6aaab8c064d Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 11 Aug 2009 13:39:40 -0400 Subject: Fix the clock face preference. I was clearing all the preferences when removing the snooze and that was clearing the clock face and visibility preference as well. Instead, remove the snooze preferences. Also, use the constant PREF_CLOCK_FACE instead of the literal string. --- src/com/android/alarmclock/Alarms.java | 16 +++++++++++----- src/com/android/alarmclock/ClockPicker.java | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index 3b7adf406..d4973fa24 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -367,12 +367,12 @@ public class Alarms { AlarmClock.PREFERENCES, 0); SharedPreferences.Editor ed = prefs.edit(); if (id == -1) { - ed.clear(); + clearSnoozePreference(ed); } else { ed.putInt(PREF_SNOOZE_ID, id); ed.putLong(PREF_SNOOZE_TIME, time); + ed.commit(); } - ed.commit(); // Set the next alert after updating the snooze. setNextAlert(context); } @@ -389,12 +389,18 @@ public class Alarms { return; } else if (snoozeId == id) { // This is the same id so clear the shared prefs. - SharedPreferences.Editor ed = prefs.edit(); - ed.clear(); - ed.commit(); + clearSnoozePreference(prefs.edit()); } } + // Helper to remove the snooze preference. Do not use clear because that + // will erase the clock preferences. + private static void clearSnoozePreference(final SharedPreferences.Editor ed) { + ed.remove(PREF_SNOOZE_ID); + ed.remove(PREF_SNOOZE_TIME); + ed.commit(); + }; + /** * If there is a snooze set, enable it in AlarmManager * @return true if snooze is set diff --git a/src/com/android/alarmclock/ClockPicker.java b/src/com/android/alarmclock/ClockPicker.java index 039f5b8a3..31150a2d5 100644 --- a/src/com/android/alarmclock/ClockPicker.java +++ b/src/com/android/alarmclock/ClockPicker.java @@ -83,7 +83,7 @@ public class ClockPicker extends Activity implements private synchronized void selectClock(int position) { SharedPreferences.Editor ed = mPrefs.edit(); - ed.putInt("face", position); + ed.putInt(AlarmClock.PREF_CLOCK_FACE, position); ed.commit(); setResult(RESULT_OK); -- cgit v1.2.3 From 67430f3d25a103cf87ba72d4034e703107b715ee Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Wed, 19 Aug 2009 16:19:15 -0400 Subject: Fix some copy-paste errors and acquire locks before launching the Activity. Acquiring the locks before launching the Activity prevents some strange pausing behavior in ActivityManager. If the screen is off, the ActivityManager attempts to pause the Activity before resuming it. --- src/com/android/alarmclock/AlarmAlert.java | 7 +++---- src/com/android/alarmclock/AlarmReceiver.java | 12 ++++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 038521b98..e78112d96 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -218,16 +218,15 @@ public class AlarmAlert extends Activity { @Override protected void onStart() { - super.onResume(); + super.onStart(); if (Log.LOGV) Log.v("AlarmAlert.onStart()"); - // Acquire a separate lock for the screen to stay on. This is necessary - // to avoid flashing the keyguard when the screen is locked. + // Acquire a separate lock for the screen to stay on. AlarmAlertWakeLock.acquireScreenWakeLock(this); } @Override protected void onStop() { - super.onPause(); + super.onStop(); if (Log.LOGV) Log.v("AlarmAlert.onStop()"); AlarmAlertWakeLock.releaseScreenLock(); } diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 784428312..7fc5b35ef 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -88,6 +88,14 @@ public class AlarmReceiver extends BroadcastReceiver { return; } + // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can + // pick it up. Also acquire the screen lock so the screen is on before + // starting the AlarmAlert activity. Do this before launching the + // AlarmAlert so that the ActivityManager does not try to pause the + // activity due to the screen being off. + AlarmAlertWakeLock.acquireCpuWakeLock(context); + AlarmAlertWakeLock.acquireScreenWakeLock(context); + /* Close dialogs and window shade */ Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.sendBroadcast(closeDialogs); @@ -154,10 +162,6 @@ public class AlarmReceiver extends BroadcastReceiver { // correct notification. NotificationManager nm = getNotificationManager(context); nm.notify(alarm.id, n); - - // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can - // pick it up. - AlarmAlertWakeLock.acquireCpuWakeLock(context); } private NotificationManager getNotificationManager(Context context) { -- cgit v1.2.3 From 1f66bd549803234e705231147c7aa4f35717697a Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Thu, 3 Sep 2009 14:18:13 -0400 Subject: Kill the alarm when a call comes in. If the alarm fires while in a call, remember the call state since registering for call state changes triggers onCallStateChanged and we don't want to kill the alarm in that case. --- src/com/android/alarmclock/AlarmKlaxon.java | 38 ++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index b63486092..a20f68ddd 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -31,11 +31,12 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Vibrator; +import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; /** - * Manages alarms and vibe. Singleton, so it can be initiated in - * AlarmReceiver and shut down in the AlarmAlert activity + * Manages alarms and vibe. Runs as a service so that it can continue to play + * if another activity overrides the AlarmAlert dialog. */ public class AlarmKlaxon extends Service { @@ -49,6 +50,8 @@ public class AlarmKlaxon extends Service { private MediaPlayer mMediaPlayer; private Alarm mCurrentAlarm; private long mStartTime; + private TelephonyManager mTelephonyManager; + private int mInitialCallState; // Internal messages private static final int KILLER = 1000; @@ -66,15 +69,37 @@ public class AlarmKlaxon extends Service { } }; + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String ignored) { + // The user might already be in a call when the alarm fires. When + // we register onCallStateChanged, we get the initial in-call state + // which kills the alarm. Check against the initial call state so + // we don't kill the alarm during a call. + if (state != TelephonyManager.CALL_STATE_IDLE + && state != mInitialCallState) { + sendKillBroadcast(mCurrentAlarm); + stopSelf(); + } + } + }; + @Override public void onCreate() { mVibrator = new Vibrator(); + // Listen for incoming calls to kill the alarm. + mTelephonyManager = + (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); + mTelephonyManager.listen( + mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); AlarmAlertWakeLock.acquireCpuWakeLock(this); } @Override public void onDestroy() { stop(); + // Stop listening for incoming calls. + mTelephonyManager.listen(mPhoneStateListener, 0); AlarmAlertWakeLock.releaseCpuLock(); } @@ -99,6 +124,9 @@ public class AlarmKlaxon extends Service { play(alarm); mCurrentAlarm = alarm; + // Record the initial call state here so that the new alarm has the + // newest state. + mInitialCallState = mTelephonyManager.getCallState(); } private void sendKillBroadcast(Alarm alarm) { @@ -147,12 +175,10 @@ public class AlarmKlaxon extends Service { }); try { - TelephonyManager tm = - (TelephonyManager) getSystemService( - Context.TELEPHONY_SERVICE); // Check if we are in a call. If we are, use the in-call alarm // resource at a low volume to not disrupt the call. - if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) { + if (mTelephonyManager.getCallState() + != TelephonyManager.CALL_STATE_IDLE) { Log.v("Using the in-call alarm"); mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); setDataSourceFromResource(getResources(), mMediaPlayer, -- cgit v1.2.3 From cf7307401797b159ca32f85e7cf38d18cececd0d Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 15 Sep 2009 09:37:57 -0400 Subject: Prevent leaking windows by listening for config changes. Remove the old database sanity check as it will not happen. Remove the strings associated with the db error as well. Update the layout of the AlarmClock activity during configuration changes so that the activity is no longer destroyed and leaking the delete alert. --- src/com/android/alarmclock/AlarmClock.java | 49 ++++++++++++------------------ 1 file changed, 20 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java index f84db846c..75477fd01 100644 --- a/src/com/android/alarmclock/AlarmClock.java +++ b/src/com/android/alarmclock/AlarmClock.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -202,33 +203,23 @@ public class AlarmClock extends Activity implements OnItemClickListener { mAm = ampm[0]; mPm = ampm[1]; - // sanity check -- no database, no clock - if (getContentResolver() == null) { - new AlertDialog.Builder(this) - .setTitle(getString(R.string.error)) - .setMessage(getString(R.string.dberror)) - .setPositiveButton( - android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }) - .setOnCancelListener( - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - finish(); - }}) - .setIcon(android.R.drawable.ic_dialog_alert) - .create().show(); - return; - } - - setContentView(R.layout.alarm_clock); mFactory = LayoutInflater.from(this); mPrefs = getSharedPreferences(PREFERENCES, 0); - mCursor = Alarms.getAlarmsCursor(getContentResolver()); + + updateLayout(); + setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateLayout(); + inflateClock(); + } + + private void updateLayout() { + setContentView(R.layout.alarm_clock); mAlarmsList = (ListView) findViewById(R.id.alarms_list); mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); mAlarmsList.setVerticalScrollBarEnabled(true); @@ -238,13 +229,12 @@ public class AlarmClock extends Activity implements OnItemClickListener { mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); mClockLayout.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - final Intent intent = new Intent(AlarmClock.this, ClockPicker.class); + final Intent intent = + new Intent(AlarmClock.this, ClockPicker.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }); - - setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); } @Override @@ -253,10 +243,11 @@ public class AlarmClock extends Activity implements OnItemClickListener { int face = mPrefs.getInt(PREF_CLOCK_FACE, 0); if (mFace != face) { - if (face < 0 || face >= AlarmClock.CLOCKS.length) + if (face < 0 || face >= AlarmClock.CLOCKS.length) { mFace = 0; - else + } else { mFace = face; + } inflateClock(); } } -- cgit v1.2.3 From 53205356eb422db2f3460425053c5fe4df8ceeda Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Tue, 15 Sep 2009 22:58:50 -0700 Subject: Use new window manager flag for turning on the screen. This allows the alert to no longer deal with a full wake lock itself, and allows the window manager to take care of not turning it on until the activity's window is actually displayed. Change-Id: I2c3529c3d12a5a9c9add71636ce958d3205832e8 --- src/com/android/alarmclock/AlarmAlert.java | 19 +++------------- src/com/android/alarmclock/AlarmAlertWakeLock.java | 25 ---------------------- src/com/android/alarmclock/AlarmReceiver.java | 6 +----- 3 files changed, 4 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index e78112d96..71871d546 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -78,7 +78,9 @@ public class AlarmAlert extends Activity { mVolumeBehavior = Integer.parseInt(vol); requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); updateLayout(); // Register to get the alarm killed intent. @@ -216,21 +218,6 @@ public class AlarmAlert extends Activity { setTitle(); } - @Override - protected void onStart() { - super.onStart(); - if (Log.LOGV) Log.v("AlarmAlert.onStart()"); - // Acquire a separate lock for the screen to stay on. - AlarmAlertWakeLock.acquireScreenWakeLock(this); - } - - @Override - protected void onStop() { - super.onStop(); - if (Log.LOGV) Log.v("AlarmAlert.onStop()"); - AlarmAlertWakeLock.releaseScreenLock(); - } - @Override public void onDestroy() { super.onDestroy(); diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java index d6ab76497..8cbcd94cb 100644 --- a/src/com/android/alarmclock/AlarmAlertWakeLock.java +++ b/src/com/android/alarmclock/AlarmAlertWakeLock.java @@ -25,7 +25,6 @@ import android.os.PowerManager; */ class AlarmAlertWakeLock { - private static PowerManager.WakeLock sScreenWakeLock; private static PowerManager.WakeLock sCpuWakeLock; static void acquireCpuWakeLock(Context context) { @@ -44,22 +43,6 @@ class AlarmAlertWakeLock { sCpuWakeLock.acquire(); } - static void acquireScreenWakeLock(Context context) { - Log.v("Acquiring screen wake lock"); - if (sScreenWakeLock != null) { - return; - } - - PowerManager pm = - (PowerManager) context.getSystemService(Context.POWER_SERVICE); - - sScreenWakeLock = pm.newWakeLock( - PowerManager.FULL_WAKE_LOCK | - PowerManager.ACQUIRE_CAUSES_WAKEUP | - PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); - sScreenWakeLock.acquire(); - } - static void releaseCpuLock() { Log.v("Releasing cpu wake lock"); if (sCpuWakeLock != null) { @@ -67,12 +50,4 @@ class AlarmAlertWakeLock { sCpuWakeLock = null; } } - - static void releaseScreenLock() { - Log.v("Releasing screen lock"); - if (sScreenWakeLock != null) { - sScreenWakeLock.release(); - sScreenWakeLock = null; - } - } } diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 7fc5b35ef..348700191 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -89,12 +89,8 @@ public class AlarmReceiver extends BroadcastReceiver { } // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can - // pick it up. Also acquire the screen lock so the screen is on before - // starting the AlarmAlert activity. Do this before launching the - // AlarmAlert so that the ActivityManager does not try to pause the - // activity due to the screen being off. + // pick it up. AlarmAlertWakeLock.acquireCpuWakeLock(context); - AlarmAlertWakeLock.acquireScreenWakeLock(context); /* Close dialogs and window shade */ Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); -- cgit v1.2.3 From 98948f9ce1dabcc79350a847baf220635b0c087a Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Fri, 18 Sep 2009 16:02:09 -0400 Subject: Fix a couple race conditions with expired alarms. Log the alarm time in a pretty format for easier debugging. Disable expired alarms when calculating the next alert. This solves 2 problems: an expired alarm remains checked in the UI and the expired alarm is sent to the alarm manager. I believe the second problem is causing bug 2080160. --- src/com/android/alarmclock/AlarmReceiver.java | 20 +++++++++++++------- src/com/android/alarmclock/Alarms.java | 5 +++++ 2 files changed, 18 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java index 348700191..97374ef5c 100644 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ b/src/com/android/alarmclock/AlarmReceiver.java @@ -27,6 +27,9 @@ import android.content.BroadcastReceiver; import android.database.Cursor; import android.os.Parcel; +import java.text.SimpleDateFormat; +import java.util.Date; + /** * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert * activity. Passes through Alarm ID. @@ -74,16 +77,17 @@ public class AlarmReceiver extends BroadcastReceiver { return; } + // Intentionally verbose: always log the alarm time to provide useful + // information in bug reports. long now = System.currentTimeMillis(); - /* FIXME Intentionally verbose: always log this until we've - fully debugged the app failing to start up */ + SimpleDateFormat format = + new SimpleDateFormat("HH:mm:ss.SSS aaa"); Log.v("AlarmReceiver.onReceive() id " + alarm.id + " setFor " - + alarm.time + " now " + now); + + format.format(new Date(alarm.time))); if (now > alarm.time + STALE_WINDOW * 1000) { if (Log.LOGV) { - Log.v("AlarmReceiver ignoring stale alarm intent id" + alarm.id - + " setFor " + alarm.time + " now " + now); + Log.v("AlarmReceiver ignoring stale alarm"); } return; } @@ -118,9 +122,11 @@ public class AlarmReceiver extends BroadcastReceiver { // Disable this alarm if it does not repeat. if (!alarm.daysOfWeek.isRepeatSet()) { Alarms.enableAlarm(context, alarm.id, false); + } else { + // Enable the next alert if there is one. The above call to + // enableAlarm will call setNextAlert so avoid calling it twice. + Alarms.setNextAlert(context); } - // Enable the next alert if there is one. - Alarms.setNextAlert(context); // Play the alarm alert and vibrate the device. Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION); diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java index d4973fa24..63a67d776 100644 --- a/src/com/android/alarmclock/Alarms.java +++ b/src/com/android/alarmclock/Alarms.java @@ -237,6 +237,7 @@ public class Alarms { public static Alarm calculateNextAlert(final Context context) { Alarm alarm = null; long minTime = Long.MAX_VALUE; + long now = System.currentTimeMillis(); Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver()); if (cursor != null) { if (cursor.moveToFirst()) { @@ -247,6 +248,10 @@ public class Alarms { if (a.time == 0) { a.time = calculateAlarm(a.hour, a.minutes, a.daysOfWeek) .getTimeInMillis(); + } else if (a.time < now) { + // Expired alarm, disable it and move along. + enableAlarmInternal(context, a, false); + continue; } if (a.time < minTime) { minTime = a.time; -- cgit v1.2.3 From 29dbeab977ead46e10efce114e1f21a55d556100 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Tue, 22 Sep 2009 13:44:12 -0700 Subject: Update to use new unlock APIs, new wallpaper theme. Also don't allow the user to press back when in the full screen alarm. Change-Id: Ic0c9658c75d3a2b3f49028f432fe226b1a0d8e9e --- src/com/android/alarmclock/AlarmAlert.java | 10 +++++--- .../android/alarmclock/AlarmAlertFullScreen.java | 29 +++++++++++----------- 2 files changed, 21 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 71871d546..1543c036b 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -78,9 +78,6 @@ public class AlarmAlert extends Activity { mVolumeBehavior = Integer.parseInt(vol); requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); updateLayout(); // Register to get the alarm killed intent. @@ -218,6 +215,13 @@ public class AlarmAlert extends Activity { setTitle(); } + @Override + protected void onStop() { + super.onStop(); + // Don't hang around. + finish(); + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/src/com/android/alarmclock/AlarmAlertFullScreen.java b/src/com/android/alarmclock/AlarmAlertFullScreen.java index 07d0d9c60..714262adc 100644 --- a/src/com/android/alarmclock/AlarmAlertFullScreen.java +++ b/src/com/android/alarmclock/AlarmAlertFullScreen.java @@ -16,10 +16,8 @@ package com.android.alarmclock; -import android.graphics.drawable.BitmapDrawable; -import android.view.View; -import android.view.Gravity; -import android.view.LayoutInflater; +import android.os.Bundle; +import android.view.WindowManager; /** * Full screen alarm alert: pops visible indicator and plays alarm tone. This @@ -27,17 +25,18 @@ import android.view.LayoutInflater; * background is the current wallpaper. */ public class AlarmAlertFullScreen extends AlarmAlert { - @Override - final protected View inflateView(LayoutInflater inflater) { - View v = inflater.inflate(R.layout.alarm_alert, null); - - // Display the wallpaper as the background. - BitmapDrawable wallpaper = (BitmapDrawable) getWallpaper(); - wallpaper.setGravity(Gravity.CENTER); - v.setBackgroundDrawable(wallpaper); - - return v; + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + } + + @Override + public void onBackPressed() { + // Don't allow back to dismiss. + return; } - } -- cgit v1.2.3 From 9d40cd573a81eba5938064a80ff507f322e96808 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 29 Sep 2009 11:21:24 -0400 Subject: Use onStartCommand since onStart is deprecated. If the intent is non-null and contains a valid alarm, return START_STICKY. --- src/com/android/alarmclock/AlarmKlaxon.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java index a20f68ddd..7205db631 100644 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ b/src/com/android/alarmclock/AlarmKlaxon.java @@ -109,13 +109,20 @@ public class AlarmKlaxon extends Service { } @Override - public void onStart(Intent intent, int startId) { + public int onStartCommand(Intent intent, int flags, int startId) { + // No intent, tell the system not to restart us. + if (intent == null) { + stopSelf(); + return START_NOT_STICKY; + } + final Alarm alarm = intent.getParcelableExtra( Alarms.ALARM_INTENT_EXTRA); if (alarm == null) { Log.v("AlarmKlaxon failed to parse the alarm from the intent"); - return; + stopSelf(); + return START_NOT_STICKY; } if (mCurrentAlarm != null) { @@ -127,6 +134,8 @@ public class AlarmKlaxon extends Service { // Record the initial call state here so that the new alarm has the // newest state. mInitialCallState = mTelephonyManager.getCallState(); + + return START_STICKY; } private void sendKillBroadcast(Alarm alarm) { -- cgit v1.2.3 From 7495b0046811971ecb0933888d7eafb4c9668ed2 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Wed, 30 Sep 2009 15:05:55 -0400 Subject: Pressing back will now save the alarm similar to how donut behaves. Do not pop the toast unless the alarm is enabled. Keep the enabled state when saving the alarm unless the time has changed. --- src/com/android/alarmclock/SetAlarm.java | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java index f744f7ada..03533af8f 100644 --- a/src/com/android/alarmclock/SetAlarm.java +++ b/src/com/android/alarmclock/SetAlarm.java @@ -54,9 +54,10 @@ public class SetAlarm extends PreferenceActivity private MenuItem mDeleteAlarmItem; private MenuItem mTestAlarmItem; - private int mId; - private int mHour; - private int mMinutes; + private int mId; + private boolean mEnabled; + private int mHour; + private int mMinutes; /** * Set an alarm. Requires an Alarms.ALARM_ID to be passed in as an @@ -92,6 +93,7 @@ public class SetAlarm extends PreferenceActivity /* load alarm details from database */ Alarm alarm = Alarms.getAlarm(getContentResolver(), mId); + mEnabled = alarm.enabled; mLabel.setText(alarm.label); mLabel.setSummary(alarm.label); mHour = alarm.hour; @@ -161,10 +163,18 @@ public class SetAlarm extends PreferenceActivity return super.onPreferenceTreeClick(preferenceScreen, preference); } + @Override + public void onBackPressed() { + saveAlarm(); + finish(); + } + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { mHour = hourOfDay; mMinutes = minute; updateTime(); + // If the time has been changed, enable the alarm. + mEnabled = true; } private void updateTime() { @@ -177,11 +187,14 @@ public class SetAlarm extends PreferenceActivity private void saveAlarm() { final String alert = mAlarmPref.getAlertString(); - Alarms.setAlarm(this, mId, true, mHour, mMinutes, + Alarms.setAlarm(this, mId, mEnabled, mHour, mMinutes, mRepeatPref.getDaysOfWeek(), mVibratePref.isChecked(), mLabel.getText(), alert); - popAlarmSetToast(this, mHour, mMinutes, mRepeatPref.getDaysOfWeek()); + if (mEnabled) { + popAlarmSetToast(this, mHour, mMinutes, + mRepeatPref.getDaysOfWeek()); + } } /** -- cgit v1.2.3 From 777585c68d7bfa7bae7a928f53e27e471300bf7c Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Wed, 30 Sep 2009 21:04:44 -0700 Subject: Fix issue #2154688: when alarm rings ,device does not wake up... ...and can't see Dismiss and Snooze button if the device is on dock Change-Id: Ib76b54f0a65930e142a3da1e1152f70d7f3ad3c7 Explanation: only the fullscreen alarm activity had the flags set to turn on the screen and dismiss the lock screen. The app picks whether to show that one or the non-fullscreen one based on whether device is currently locked, which is correct, but with the introduction of docks, locked != screen off. In particular, the desk dock keeps the keyguard dismissed while the device is in it. The fix is to just set these flags for both alert activities. That seems generally right for many reasons. --- src/com/android/alarmclock/AlarmAlert.java | 4 ++++ src/com/android/alarmclock/AlarmAlertFullScreen.java | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java index 1543c036b..89a866a99 100644 --- a/src/com/android/alarmclock/AlarmAlert.java +++ b/src/com/android/alarmclock/AlarmAlert.java @@ -78,6 +78,10 @@ public class AlarmAlert extends Activity { mVolumeBehavior = Integer.parseInt(vol); requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); updateLayout(); // Register to get the alarm killed intent. diff --git a/src/com/android/alarmclock/AlarmAlertFullScreen.java b/src/com/android/alarmclock/AlarmAlertFullScreen.java index 714262adc..c00ffce58 100644 --- a/src/com/android/alarmclock/AlarmAlertFullScreen.java +++ b/src/com/android/alarmclock/AlarmAlertFullScreen.java @@ -28,10 +28,6 @@ public class AlarmAlertFullScreen extends AlarmAlert { @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); } @Override -- cgit v1.2.3 From fa954248b41b567dfa2ddd7bd063d3936a51758e Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 16 Oct 2009 16:12:30 -0400 Subject: Moving things around a little in the new DeskClock. - Package is now com.android.deskclock - Launches in response to insertion in desk dock or DESK dialercode TODO next: split the AlarmClock activity into a DeskClock and AlarmList (or similar). --- src/com/android/alarmclock/Alarm.java | 341 -------------- src/com/android/alarmclock/AlarmAlert.java | 267 ----------- .../android/alarmclock/AlarmAlertFullScreen.java | 38 -- src/com/android/alarmclock/AlarmAlertWakeLock.java | 53 --- src/com/android/alarmclock/AlarmClock.java | 383 --------------- src/com/android/alarmclock/AlarmInitReceiver.java | 44 -- src/com/android/alarmclock/AlarmKlaxon.java | 287 ------------ src/com/android/alarmclock/AlarmPreference.java | 65 --- src/com/android/alarmclock/AlarmProvider.java | 237 ---------- src/com/android/alarmclock/AlarmReceiver.java | 202 -------- src/com/android/alarmclock/Alarms.java | 513 --------------------- .../alarmclock/AnalogAppWidgetProvider.java | 66 --- src/com/android/alarmclock/ClockPicker.java | 119 ----- src/com/android/alarmclock/DigitalClock.java | 201 -------- src/com/android/alarmclock/Log.java | 42 -- src/com/android/alarmclock/RepeatPreference.java | 85 ---- src/com/android/alarmclock/SetAlarm.java | 321 ------------- src/com/android/alarmclock/SettingsActivity.java | 103 ----- src/com/android/alarmclock/ToastMaster.java | 41 -- src/com/android/deskclock/Alarm.java | 341 ++++++++++++++ src/com/android/deskclock/AlarmAlert.java | 267 +++++++++++ .../android/deskclock/AlarmAlertFullScreen.java | 38 ++ src/com/android/deskclock/AlarmAlertWakeLock.java | 53 +++ src/com/android/deskclock/AlarmClock.java | 383 +++++++++++++++ src/com/android/deskclock/AlarmInitReceiver.java | 44 ++ src/com/android/deskclock/AlarmKlaxon.java | 287 ++++++++++++ src/com/android/deskclock/AlarmPreference.java | 65 +++ src/com/android/deskclock/AlarmProvider.java | 237 ++++++++++ src/com/android/deskclock/AlarmReceiver.java | 202 ++++++++ src/com/android/deskclock/Alarms.java | 513 +++++++++++++++++++++ .../android/deskclock/AnalogAppWidgetProvider.java | 66 +++ src/com/android/deskclock/ClockPicker.java | 119 +++++ src/com/android/deskclock/DigitalClock.java | 201 ++++++++ src/com/android/deskclock/DockEventReceiver.java | 73 +++ src/com/android/deskclock/Log.java | 42 ++ src/com/android/deskclock/RepeatPreference.java | 85 ++++ src/com/android/deskclock/SetAlarm.java | 321 +++++++++++++ src/com/android/deskclock/SettingsActivity.java | 103 +++++ src/com/android/deskclock/ToastMaster.java | 41 ++ 39 files changed, 3481 insertions(+), 3408 deletions(-) delete mode 100644 src/com/android/alarmclock/Alarm.java delete mode 100644 src/com/android/alarmclock/AlarmAlert.java delete mode 100644 src/com/android/alarmclock/AlarmAlertFullScreen.java delete mode 100644 src/com/android/alarmclock/AlarmAlertWakeLock.java delete mode 100644 src/com/android/alarmclock/AlarmClock.java delete mode 100644 src/com/android/alarmclock/AlarmInitReceiver.java delete mode 100644 src/com/android/alarmclock/AlarmKlaxon.java delete mode 100644 src/com/android/alarmclock/AlarmPreference.java delete mode 100644 src/com/android/alarmclock/AlarmProvider.java delete mode 100644 src/com/android/alarmclock/AlarmReceiver.java delete mode 100644 src/com/android/alarmclock/Alarms.java delete mode 100644 src/com/android/alarmclock/AnalogAppWidgetProvider.java delete mode 100644 src/com/android/alarmclock/ClockPicker.java delete mode 100644 src/com/android/alarmclock/DigitalClock.java delete mode 100644 src/com/android/alarmclock/Log.java delete mode 100644 src/com/android/alarmclock/RepeatPreference.java delete mode 100644 src/com/android/alarmclock/SetAlarm.java delete mode 100644 src/com/android/alarmclock/SettingsActivity.java delete mode 100644 src/com/android/alarmclock/ToastMaster.java create mode 100644 src/com/android/deskclock/Alarm.java create mode 100644 src/com/android/deskclock/AlarmAlert.java create mode 100644 src/com/android/deskclock/AlarmAlertFullScreen.java create mode 100644 src/com/android/deskclock/AlarmAlertWakeLock.java create mode 100644 src/com/android/deskclock/AlarmClock.java create mode 100644 src/com/android/deskclock/AlarmInitReceiver.java create mode 100644 src/com/android/deskclock/AlarmKlaxon.java create mode 100644 src/com/android/deskclock/AlarmPreference.java create mode 100644 src/com/android/deskclock/AlarmProvider.java create mode 100644 src/com/android/deskclock/AlarmReceiver.java create mode 100644 src/com/android/deskclock/Alarms.java create mode 100644 src/com/android/deskclock/AnalogAppWidgetProvider.java create mode 100644 src/com/android/deskclock/ClockPicker.java create mode 100644 src/com/android/deskclock/DigitalClock.java create mode 100644 src/com/android/deskclock/DockEventReceiver.java create mode 100644 src/com/android/deskclock/Log.java create mode 100644 src/com/android/deskclock/RepeatPreference.java create mode 100644 src/com/android/deskclock/SetAlarm.java create mode 100644 src/com/android/deskclock/SettingsActivity.java create mode 100644 src/com/android/deskclock/ToastMaster.java (limited to 'src') diff --git a/src/com/android/alarmclock/Alarm.java b/src/com/android/alarmclock/Alarm.java deleted file mode 100644 index 350b7b496..000000000 --- a/src/com/android/alarmclock/Alarm.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2009 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.alarmclock; - -import android.content.Context; -import android.database.Cursor; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.BaseColumns; -import android.text.format.DateFormat; - -import java.text.DateFormatSymbols; -import java.util.Calendar; - -public final class Alarm implements Parcelable { - - ////////////////////////////// - // Parcelable apis - ////////////////////////////// - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public Alarm createFromParcel(Parcel p) { - return new Alarm(p); - } - - public Alarm[] newArray(int size) { - return new Alarm[size]; - } - }; - - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel p, int flags) { - p.writeInt(id); - p.writeInt(enabled ? 1 : 0); - p.writeInt(hour); - p.writeInt(minutes); - p.writeInt(daysOfWeek.getCoded()); - p.writeLong(time); - p.writeInt(vibrate ? 1 : 0); - p.writeString(label); - p.writeParcelable(alert, flags); - p.writeInt(silent ? 1 : 0); - } - ////////////////////////////// - // end Parcelable apis - ////////////////////////////// - - ////////////////////////////// - // Column definitions - ////////////////////////////// - public static class Columns implements BaseColumns { - /** - * The content:// style URL for this table - */ - public static final Uri CONTENT_URI = - Uri.parse("content://com.android.alarmclock/alarm"); - - /** - * Hour in 24-hour localtime 0 - 23. - *

Type: INTEGER

- */ - public static final String HOUR = "hour"; - - /** - * Minutes in localtime 0 - 59 - *

Type: INTEGER

- */ - public static final String MINUTES = "minutes"; - - /** - * Days of week coded as integer - *

Type: INTEGER

- */ - public static final String DAYS_OF_WEEK = "daysofweek"; - - /** - * Alarm time in UTC milliseconds from the epoch. - *

Type: INTEGER

- */ - public static final String ALARM_TIME = "alarmtime"; - - /** - * True if alarm is active - *

Type: BOOLEAN

- */ - public static final String ENABLED = "enabled"; - - /** - * True if alarm should vibrate - *

Type: BOOLEAN

- */ - public static final String VIBRATE = "vibrate"; - - /** - * Message to show when alarm triggers - * Note: not currently used - *

Type: STRING

- */ - public static final String MESSAGE = "message"; - - /** - * Audio alert to play when alarm triggers - *

Type: STRING

- */ - public static final String ALERT = "alert"; - - /** - * The default sort order for this table - */ - public static final String DEFAULT_SORT_ORDER = _ID + " ASC"; - - // Used when filtering enabled alarms. - public static final String WHERE_ENABLED = ENABLED + "=1"; - - static final String[] ALARM_QUERY_COLUMNS = { - _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME, - ENABLED, VIBRATE, MESSAGE, ALERT }; - - /** - * These save calls to cursor.getColumnIndexOrThrow() - * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS - */ - public static final int ALARM_ID_INDEX = 0; - public static final int ALARM_HOUR_INDEX = 1; - public static final int ALARM_MINUTES_INDEX = 2; - public static final int ALARM_DAYS_OF_WEEK_INDEX = 3; - public static final int ALARM_TIME_INDEX = 4; - public static final int ALARM_ENABLED_INDEX = 5; - public static final int ALARM_VIBRATE_INDEX = 6; - public static final int ALARM_MESSAGE_INDEX = 7; - public static final int ALARM_ALERT_INDEX = 8; - } - ////////////////////////////// - // End column definitions - ////////////////////////////// - - // Public fields - public int id; - public boolean enabled; - public int hour; - public int minutes; - public DaysOfWeek daysOfWeek; - public long time; - public boolean vibrate; - public String label; - public Uri alert; - public boolean silent; - - public Alarm(Cursor c) { - id = c.getInt(Columns.ALARM_ID_INDEX); - enabled = c.getInt(Columns.ALARM_ENABLED_INDEX) == 1; - hour = c.getInt(Columns.ALARM_HOUR_INDEX); - minutes = c.getInt(Columns.ALARM_MINUTES_INDEX); - daysOfWeek = new DaysOfWeek(c.getInt(Columns.ALARM_DAYS_OF_WEEK_INDEX)); - time = c.getLong(Columns.ALARM_TIME_INDEX); - vibrate = c.getInt(Columns.ALARM_VIBRATE_INDEX) == 1; - label = c.getString(Columns.ALARM_MESSAGE_INDEX); - String alertString = c.getString(Columns.ALARM_ALERT_INDEX); - if (Alarms.ALARM_ALERT_SILENT.equals(alertString)) { - if (Log.LOGV) { - Log.v("Alarm is marked as silent"); - } - silent = true; - } else { - if (alertString != null && alertString.length() != 0) { - alert = Uri.parse(alertString); - } - - // If the database alert is null or it failed to parse, use the - // default alert. - if (alert == null) { - alert = RingtoneManager.getDefaultUri( - RingtoneManager.TYPE_ALARM); - } - } - } - - public Alarm(Parcel p) { - id = p.readInt(); - enabled = p.readInt() == 1; - hour = p.readInt(); - minutes = p.readInt(); - daysOfWeek = new DaysOfWeek(p.readInt()); - time = p.readLong(); - vibrate = p.readInt() == 1; - label = p.readString(); - alert = (Uri) p.readParcelable(null); - silent = p.readInt() == 1; - } - - public String getLabelOrDefault(Context context) { - if (label == null || label.length() == 0) { - return context.getString(R.string.default_label); - } - return label; - } - - /* - * Days of week code as a single int. - * 0x00: no day - * 0x01: Monday - * 0x02: Tuesday - * 0x04: Wednesday - * 0x08: Thursday - * 0x10: Friday - * 0x20: Saturday - * 0x40: Sunday - */ - static final class DaysOfWeek { - - private static int[] DAY_MAP = new int[] { - Calendar.MONDAY, - Calendar.TUESDAY, - Calendar.WEDNESDAY, - Calendar.THURSDAY, - Calendar.FRIDAY, - Calendar.SATURDAY, - Calendar.SUNDAY, - }; - - // Bitmask of all repeating days - private int mDays; - - DaysOfWeek(int days) { - mDays = days; - } - - public String toString(Context context, boolean showNever) { - StringBuilder ret = new StringBuilder(); - - // no days - if (mDays == 0) { - return showNever ? - context.getText(R.string.never).toString() : ""; - } - - // every day - if (mDays == 0x7f) { - return context.getText(R.string.every_day).toString(); - } - - // count selected days - int dayCount = 0, days = mDays; - while (days > 0) { - if ((days & 1) == 1) dayCount++; - days >>= 1; - } - - // short or long form? - DateFormatSymbols dfs = new DateFormatSymbols(); - String[] dayList = (dayCount > 1) ? - dfs.getShortWeekdays() : - dfs.getWeekdays(); - - // selected days - for (int i = 0; i < 7; i++) { - if ((mDays & (1 << i)) != 0) { - ret.append(dayList[DAY_MAP[i]]); - dayCount -= 1; - if (dayCount > 0) ret.append( - context.getText(R.string.day_concat)); - } - } - return ret.toString(); - } - - private boolean isSet(int day) { - return ((mDays & (1 << day)) > 0); - } - - public void set(int day, boolean set) { - if (set) { - mDays |= (1 << day); - } else { - mDays &= ~(1 << day); - } - } - - public void set(DaysOfWeek dow) { - mDays = dow.mDays; - } - - public int getCoded() { - return mDays; - } - - // Returns days of week encoded in an array of booleans. - public boolean[] getBooleanArray() { - boolean[] ret = new boolean[7]; - for (int i = 0; i < 7; i++) { - ret[i] = isSet(i); - } - return ret; - } - - public boolean isRepeatSet() { - return mDays != 0; - } - - /** - * returns number of days from today until next alarm - * @param c must be set to today - */ - public int getNextAlarm(Calendar c) { - if (mDays == 0) { - return -1; - } - - int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7; - - int day = 0; - int dayCount = 0; - for (; dayCount < 7; dayCount++) { - day = (today + dayCount) % 7; - if (isSet(day)) { - break; - } - } - return dayCount; - } - } -} diff --git a/src/com/android/alarmclock/AlarmAlert.java b/src/com/android/alarmclock/AlarmAlert.java deleted file mode 100644 index 89a866a99..000000000 --- a/src/com/android/alarmclock/AlarmAlert.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.Activity; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.BroadcastReceiver; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.LayoutInflater; -import android.view.Window; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.Toast; -import android.widget.TextView; - -import java.util.Calendar; - -/** - * Alarm Clock alarm alert: pops visible indicator and plays alarm - * tone - */ -public class AlarmAlert extends Activity { - - // These defaults must match the values in res/xml/settings.xml - private static final String DEFAULT_SNOOZE = "10"; - private static final String DEFAULT_VOLUME_BEHAVIOR = "2"; - - private Alarm mAlarm; - private int mVolumeBehavior; - - // Receives the ALARM_KILLED action from the AlarmKlaxon. - private BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Alarm alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); - if (mAlarm.id == alarm.id) { - dismiss(true); - } - } - }; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mAlarm = getIntent().getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); - - // Get the volume/camera button behavior setting - final String vol = - PreferenceManager.getDefaultSharedPreferences(this) - .getString(SettingsActivity.KEY_VOLUME_BEHAVIOR, - DEFAULT_VOLUME_BEHAVIOR); - mVolumeBehavior = Integer.parseInt(vol); - - requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); - updateLayout(); - - // Register to get the alarm killed intent. - registerReceiver(mReceiver, new IntentFilter(Alarms.ALARM_KILLED)); - } - - private void setTitle() { - String label = mAlarm.getLabelOrDefault(this); - TextView title = (TextView) findViewById(R.id.alertTitle); - title.setText(label); - } - - // This method is overwritten in AlarmAlertFullScreen in order to show a - // full activity with the wallpaper as the background. - protected View inflateView(LayoutInflater inflater) { - return inflater.inflate(R.layout.alarm_alert, null); - } - - private void updateLayout() { - LayoutInflater inflater = LayoutInflater.from(this); - - setContentView(inflateView(inflater)); - - /* set clock face */ - SharedPreferences settings = - getSharedPreferences(AlarmClock.PREFERENCES, 0); - int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); - if (face < 0 || face >= AlarmClock.CLOCKS.length) { - face = 0; - } - ViewGroup clockView = (ViewGroup) findViewById(R.id.clockView); - inflater.inflate(AlarmClock.CLOCKS[face], clockView); - View clockLayout = findViewById(R.id.clock); - if (clockLayout instanceof DigitalClock) { - ((DigitalClock) clockLayout).setAnimate(); - } - - /* snooze behavior: pop a snooze confirmation view, kick alarm - manager. */ - Button snooze = (Button) findViewById(R.id.snooze); - snooze.requestFocus(); - snooze.setOnClickListener(new Button.OnClickListener() { - public void onClick(View v) { - snooze(); - } - }); - - /* dismiss button: close notification */ - findViewById(R.id.dismiss).setOnClickListener( - new Button.OnClickListener() { - public void onClick(View v) { - dismiss(false); - } - }); - - /* Set the title from the passed in alarm */ - setTitle(); - } - - // Attempt to snooze this alert. - private void snooze() { - final String snooze = - PreferenceManager.getDefaultSharedPreferences(this) - .getString(SettingsActivity.KEY_ALARM_SNOOZE, DEFAULT_SNOOZE); - int snoozeMinutes = Integer.parseInt(snooze); - - final long snoozeTime = System.currentTimeMillis() - + (1000 * 60 * snoozeMinutes); - Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarm.id, snoozeTime); - - // Get the display time for the snooze and update the notification. - final Calendar c = Calendar.getInstance(); - c.setTimeInMillis(snoozeTime); - - // Append (snoozed) to the label. - String label = mAlarm.getLabelOrDefault(this); - label = getString(R.string.alarm_notify_snooze_label, label); - - // Notify the user that the alarm has been snoozed. - Intent cancelSnooze = new Intent(this, AlarmReceiver.class); - cancelSnooze.setAction(Alarms.CANCEL_SNOOZE); - cancelSnooze.putExtra(Alarms.ALARM_ID, mAlarm.id); - PendingIntent broadcast = - PendingIntent.getBroadcast(this, mAlarm.id, cancelSnooze, 0); - NotificationManager nm = getNotificationManager(); - Notification n = new Notification(R.drawable.stat_notify_alarm, - label, 0); - n.setLatestEventInfo(this, label, - getString(R.string.alarm_notify_snooze_text, - Alarms.formatTime(this, c)), broadcast); - n.deleteIntent = broadcast; - n.flags |= Notification.FLAG_AUTO_CANCEL; - nm.notify(mAlarm.id, n); - - String displayTime = getString(R.string.alarm_alert_snooze_set, - snoozeMinutes); - // Intentionally log the snooze time for debugging. - Log.v(displayTime); - - // Display the snooze minutes in a toast. - Toast.makeText(AlarmAlert.this, displayTime, Toast.LENGTH_LONG).show(); - stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); - finish(); - } - - private NotificationManager getNotificationManager() { - return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - } - - // Dismiss the alarm. - private void dismiss(boolean killed) { - // The service told us that the alarm has been killed, do not modify - // the notification or stop the service. - if (!killed) { - // Cancel the notification and stop playing the alarm - NotificationManager nm = getNotificationManager(); - nm.cancel(mAlarm.id); - stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); - } - finish(); - } - - /** - * this is called when a second alarm is triggered while a - * previous alert window is still active. - */ - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); - - mAlarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); - - setTitle(); - } - - @Override - protected void onStop() { - super.onStop(); - // Don't hang around. - finish(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (Log.LOGV) Log.v("AlarmAlert.onDestroy()"); - // No longer care about the alarm being killed. - unregisterReceiver(mReceiver); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - // Do this on key down to handle a few of the system keys. - boolean up = event.getAction() == KeyEvent.ACTION_UP; - switch (event.getKeyCode()) { - // Volume keys and camera keys dismiss the alarm - case KeyEvent.KEYCODE_VOLUME_UP: - case KeyEvent.KEYCODE_VOLUME_DOWN: - case KeyEvent.KEYCODE_CAMERA: - case KeyEvent.KEYCODE_FOCUS: - if (up) { - switch (mVolumeBehavior) { - case 1: - snooze(); - break; - - case 2: - dismiss(false); - break; - - default: - break; - } - } - return true; - default: - break; - } - return super.dispatchKeyEvent(event); - } -} diff --git a/src/com/android/alarmclock/AlarmAlertFullScreen.java b/src/com/android/alarmclock/AlarmAlertFullScreen.java deleted file mode 100644 index c00ffce58..000000000 --- a/src/com/android/alarmclock/AlarmAlertFullScreen.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2009 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.alarmclock; - -import android.os.Bundle; -import android.view.WindowManager; - -/** - * Full screen alarm alert: pops visible indicator and plays alarm tone. This - * activity displays the alert in full screen in order to be secure. The - * background is the current wallpaper. - */ -public class AlarmAlertFullScreen extends AlarmAlert { - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - } - - @Override - public void onBackPressed() { - // Don't allow back to dismiss. - return; - } -} diff --git a/src/com/android/alarmclock/AlarmAlertWakeLock.java b/src/com/android/alarmclock/AlarmAlertWakeLock.java deleted file mode 100644 index 8cbcd94cb..000000000 --- a/src/com/android/alarmclock/AlarmAlertWakeLock.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.alarmclock; - -import android.content.Context; -import android.os.PowerManager; - -/** - * Hold a wakelock that can be acquired in the AlarmReceiver and - * released in the AlarmAlert activity - */ -class AlarmAlertWakeLock { - - private static PowerManager.WakeLock sCpuWakeLock; - - static void acquireCpuWakeLock(Context context) { - Log.v("Acquiring cpu wake lock"); - if (sCpuWakeLock != null) { - return; - } - - PowerManager pm = - (PowerManager) context.getSystemService(Context.POWER_SERVICE); - - sCpuWakeLock = pm.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK | - PowerManager.ACQUIRE_CAUSES_WAKEUP | - PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); - sCpuWakeLock.acquire(); - } - - static void releaseCpuLock() { - Log.v("Releasing cpu wake lock"); - if (sCpuWakeLock != null) { - sCpuWakeLock.release(); - sCpuWakeLock = null; - } - } -} diff --git a/src/com/android/alarmclock/AlarmClock.java b/src/com/android/alarmclock/AlarmClock.java deleted file mode 100644 index 75477fd01..000000000 --- a/src/com/android/alarmclock/AlarmClock.java +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.provider.Settings; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnCreateContextMenuListener; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.CursorAdapter; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.CheckBox; - -import java.util.Calendar; -import java.text.DateFormatSymbols; - -/** - * AlarmClock application. - */ -public class AlarmClock extends Activity implements OnItemClickListener { - - final static String PREFERENCES = "AlarmClock"; - final static String PREF_CLOCK_FACE = "face"; - final static String PREF_SHOW_CLOCK = "show_clock"; - - /** Cap alarm count at this number */ - final static int MAX_ALARM_COUNT = 12; - - /** This must be false for production. If true, turns on logging, - test code, etc. */ - final static boolean DEBUG = false; - - private SharedPreferences mPrefs; - private LayoutInflater mFactory; - private ViewGroup mClockLayout; - private View mClock = null; - private ListView mAlarmsList; - private Cursor mCursor; - - private String mAm, mPm; - - /** - * Which clock face to show - */ - private int mFace = -1; - - /* - * FIXME: it would be nice for this to live in an xml config file. - */ - final static int[] CLOCKS = { - R.layout.clock_basic_bw, - R.layout.clock_googly, - R.layout.clock_droid2, - R.layout.clock_droids, - R.layout.digital_clock - }; - - private class AlarmTimeAdapter extends CursorAdapter { - public AlarmTimeAdapter(Context context, Cursor cursor) { - super(context, cursor); - } - - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View ret = mFactory.inflate(R.layout.alarm_time, parent, false); - - ((TextView) ret.findViewById(R.id.am)).setText(mAm); - ((TextView) ret.findViewById(R.id.pm)).setText(mPm); - - DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); - digitalClock.setLive(false); - if (Log.LOGV) Log.v("newView " + cursor.getPosition()); - return ret; - } - - public void bindView(View view, Context context, Cursor cursor) { - final Alarm alarm = new Alarm(cursor); - - CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton); - onButton.setChecked(alarm.enabled); - onButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - boolean isChecked = ((CheckBox) v).isChecked(); - Alarms.enableAlarm(AlarmClock.this, alarm.id, - isChecked); - if (isChecked) { - SetAlarm.popAlarmSetToast(AlarmClock.this, - alarm.hour, alarm.minutes, alarm.daysOfWeek); - } - } - }); - - DigitalClock digitalClock = - (DigitalClock) view.findViewById(R.id.digitalClock); - - // set the alarm text - final Calendar c = Calendar.getInstance(); - c.set(Calendar.HOUR_OF_DAY, alarm.hour); - c.set(Calendar.MINUTE, alarm.minutes); - digitalClock.updateTime(c); - - // Set the repeat text or leave it blank if it does not repeat. - TextView daysOfWeekView = - (TextView) digitalClock.findViewById(R.id.daysOfWeek); - final String daysOfWeekStr = - alarm.daysOfWeek.toString(AlarmClock.this, false); - if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) { - daysOfWeekView.setText(daysOfWeekStr); - daysOfWeekView.setVisibility(View.VISIBLE); - } else { - daysOfWeekView.setVisibility(View.GONE); - } - - // Display the label - TextView labelView = - (TextView) digitalClock.findViewById(R.id.label); - if (alarm.label != null && alarm.label.length() != 0) { - labelView.setText(alarm.label); - labelView.setVisibility(View.VISIBLE); - } else { - labelView.setVisibility(View.GONE); - } - } - }; - - @Override - public boolean onContextItemSelected(final MenuItem item) { - final AdapterContextMenuInfo info = - (AdapterContextMenuInfo) item.getMenuInfo(); - final int id = (int) info.id; - switch (item.getItemId()) { - case R.id.delete_alarm: - // Confirm that the alarm will be deleted. - new AlertDialog.Builder(this) - .setTitle(getString(R.string.delete_alarm)) - .setMessage(getString(R.string.delete_alarm_confirm)) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface d, - int w) { - Alarms.deleteAlarm(AlarmClock.this, id); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .show(); - return true; - - case R.id.enable_alarm: - final Cursor c = (Cursor) mAlarmsList.getAdapter() - .getItem(info.position); - final Alarm alarm = new Alarm(c); - Alarms.enableAlarm(this, alarm.id, !alarm.enabled); - if (!alarm.enabled) { - SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes, - alarm.daysOfWeek); - } - return true; - - default: - break; - } - return super.onContextItemSelected(item); - } - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - String[] ampm = new DateFormatSymbols().getAmPmStrings(); - mAm = ampm[0]; - mPm = ampm[1]; - - mFactory = LayoutInflater.from(this); - mPrefs = getSharedPreferences(PREFERENCES, 0); - mCursor = Alarms.getAlarmsCursor(getContentResolver()); - - updateLayout(); - setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - updateLayout(); - inflateClock(); - } - - private void updateLayout() { - setContentView(R.layout.alarm_clock); - mAlarmsList = (ListView) findViewById(R.id.alarms_list); - mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); - mAlarmsList.setVerticalScrollBarEnabled(true); - mAlarmsList.setOnItemClickListener(this); - mAlarmsList.setOnCreateContextMenuListener(this); - - mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); - mClockLayout.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - final Intent intent = - new Intent(AlarmClock.this, ClockPicker.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - - int face = mPrefs.getInt(PREF_CLOCK_FACE, 0); - if (mFace != face) { - if (face < 0 || face >= AlarmClock.CLOCKS.length) { - mFace = 0; - } else { - mFace = face; - } - inflateClock(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - ToastMaster.cancelToast(); - mCursor.deactivate(); - } - - protected void inflateClock() { - if (mClock != null) { - mClockLayout.removeView(mClock); - } - - LayoutInflater.from(this).inflate(CLOCKS[mFace], mClockLayout); - mClock = findViewById(R.id.clock); - - TextView am = (TextView) findViewById(R.id.am); - TextView pm = (TextView) findViewById(R.id.pm); - - if (am != null) { - am.setText(mAm); - } - if (pm != null) { - pm.setText(mPm); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate our menu. - getMenuInflater().inflate(R.menu.main_menu, menu); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, - ContextMenuInfo menuInfo) { - // Inflate the menu from xml. - getMenuInflater().inflate(R.menu.context_menu, menu); - - // Use the current item to create a custom view for the header. - final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - final Cursor c = - (Cursor) mAlarmsList.getAdapter().getItem((int) info.position); - final Alarm alarm = new Alarm(c); - - // Construct the Calendar to compute the time. - final Calendar cal = Calendar.getInstance(); - cal.set(Calendar.HOUR_OF_DAY, alarm.hour); - cal.set(Calendar.MINUTE, alarm.minutes); - final String time = Alarms.formatTime(this, cal); - - // Inflate the custom view and set each TextView's text. - final View v = mFactory.inflate(R.layout.context_menu_header, null); - TextView textView = (TextView) v.findViewById(R.id.header_time); - textView.setText(time); - textView = (TextView) v.findViewById(R.id.header_label); - textView.setText(alarm.label); - - // Set the custom view on the menu. - menu.setHeaderView(v); - // Change the text to "disable" if the alarm is already enabled. - if (alarm.enabled) { - menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm); - } - } - - public void onItemClick(AdapterView parent, View v, int pos, long id) { - Intent intent = new Intent(this, SetAlarm.class); - intent.putExtra(Alarms.ALARM_ID, (int) id); - startActivity(intent); - } - - /** - * Only allow user to add a new alarm if there are fewer than - * MAX_ALARM_COUNT - */ - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.findItem(R.id.menu_add_alarm).setVisible( - mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); - menu.findItem(R.id.menu_toggle_clock).setTitle( - getClockVisibility() ? R.string.hide_clock - : R.string.show_clock); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_add_alarm: - Uri uri = Alarms.addAlarm(getContentResolver()); - // FIXME: scroll to new item? - String segment = uri.getPathSegments().get(1); - int newId = Integer.parseInt(segment); - if (Log.LOGV) { - Log.v("In AlarmClock, new alarm id = " + newId); - } - Intent intent = new Intent(this, SetAlarm.class); - intent.putExtra(Alarms.ALARM_ID, newId); - startActivity(intent); - return true; - - case R.id.menu_toggle_clock: - setClockVisibility(!getClockVisibility()); - saveClockVisibility(); - return true; - - case R.id.menu_settings: - startActivity(new Intent(this, SettingsActivity.class)); - return true; - } - - return super.onOptionsItemSelected(item); - } - - - private boolean getClockVisibility() { - return mClockLayout.getVisibility() == View.VISIBLE; - } - - private void setClockVisibility(boolean visible) { - mClockLayout.setVisibility(visible ? View.VISIBLE : View.GONE); - } - - private void saveClockVisibility() { - mPrefs.edit().putBoolean(PREF_SHOW_CLOCK, getClockVisibility()).commit(); - } -} diff --git a/src/com/android/alarmclock/AlarmInitReceiver.java b/src/com/android/alarmclock/AlarmInitReceiver.java deleted file mode 100644 index 8657e031f..000000000 --- a/src/com/android/alarmclock/AlarmInitReceiver.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; - -public class AlarmInitReceiver extends BroadcastReceiver { - - /** - * Sets alarm on ACTION_BOOT_COMPLETED. Resets alarm on - * TIME_SET, TIMEZONE_CHANGED - */ - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Log.LOGV) Log.v("AlarmInitReceiver" + action); - - if (context.getContentResolver() == null) { - Log.e("AlarmInitReceiver: FAILURE unable to get content resolver. Alarms inactive."); - return; - } - if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - Alarms.saveSnoozeAlert(context, -1, -1); - Alarms.disableExpiredAlarms(context); - } - Alarms.setNextAlert(context); - } -} diff --git a/src/com/android/alarmclock/AlarmKlaxon.java b/src/com/android/alarmclock/AlarmKlaxon.java deleted file mode 100644 index 7205db631..000000000 --- a/src/com/android/alarmclock/AlarmKlaxon.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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.alarmclock; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.res.AssetFileDescriptor; -import android.content.res.Resources; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnErrorListener; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Vibrator; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; - -/** - * Manages alarms and vibe. Runs as a service so that it can continue to play - * if another activity overrides the AlarmAlert dialog. - */ -public class AlarmKlaxon extends Service { - - /** Play alarm up to 10 minutes before silencing */ - private static final int ALARM_TIMEOUT_SECONDS = 10 * 60; - - private static final long[] sVibratePattern = new long[] { 500, 500 }; - - private boolean mPlaying = false; - private Vibrator mVibrator; - private MediaPlayer mMediaPlayer; - private Alarm mCurrentAlarm; - private long mStartTime; - private TelephonyManager mTelephonyManager; - private int mInitialCallState; - - // Internal messages - private static final int KILLER = 1000; - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case KILLER: - if (Log.LOGV) { - Log.v("*********** Alarm killer triggered ***********"); - } - sendKillBroadcast((Alarm) msg.obj); - stopSelf(); - break; - } - } - }; - - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onCallStateChanged(int state, String ignored) { - // The user might already be in a call when the alarm fires. When - // we register onCallStateChanged, we get the initial in-call state - // which kills the alarm. Check against the initial call state so - // we don't kill the alarm during a call. - if (state != TelephonyManager.CALL_STATE_IDLE - && state != mInitialCallState) { - sendKillBroadcast(mCurrentAlarm); - stopSelf(); - } - } - }; - - @Override - public void onCreate() { - mVibrator = new Vibrator(); - // Listen for incoming calls to kill the alarm. - mTelephonyManager = - (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); - mTelephonyManager.listen( - mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); - AlarmAlertWakeLock.acquireCpuWakeLock(this); - } - - @Override - public void onDestroy() { - stop(); - // Stop listening for incoming calls. - mTelephonyManager.listen(mPhoneStateListener, 0); - AlarmAlertWakeLock.releaseCpuLock(); - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // No intent, tell the system not to restart us. - if (intent == null) { - stopSelf(); - return START_NOT_STICKY; - } - - final Alarm alarm = intent.getParcelableExtra( - Alarms.ALARM_INTENT_EXTRA); - - if (alarm == null) { - Log.v("AlarmKlaxon failed to parse the alarm from the intent"); - stopSelf(); - return START_NOT_STICKY; - } - - if (mCurrentAlarm != null) { - sendKillBroadcast(mCurrentAlarm); - } - - play(alarm); - mCurrentAlarm = alarm; - // Record the initial call state here so that the new alarm has the - // newest state. - mInitialCallState = mTelephonyManager.getCallState(); - - return START_STICKY; - } - - private void sendKillBroadcast(Alarm alarm) { - long millis = System.currentTimeMillis() - mStartTime; - int minutes = (int) Math.round(millis / 60000.0); - Intent alarmKilled = new Intent(Alarms.ALARM_KILLED); - alarmKilled.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); - alarmKilled.putExtra(Alarms.ALARM_KILLED_TIMEOUT, minutes); - sendBroadcast(alarmKilled); - } - - // Volume suggested by media team for in-call alarms. - private static final float IN_CALL_VOLUME = 0.125f; - - private void play(Alarm alarm) { - // stop() checks to see if we are already playing. - stop(); - - if (Log.LOGV) { - Log.v("AlarmKlaxon.play() " + alarm.id + " alert " + alarm.alert); - } - - if (!alarm.silent) { - Uri alert = alarm.alert; - // Fall back on the default alarm if the database does not have an - // alarm stored. - if (alert == null) { - alert = RingtoneManager.getDefaultUri( - RingtoneManager.TYPE_ALARM); - if (Log.LOGV) { - Log.v("Using default alarm: " + alert.toString()); - } - } - - // TODO: Reuse mMediaPlayer instead of creating a new one and/or use - // RingtoneManager. - mMediaPlayer = new MediaPlayer(); - mMediaPlayer.setOnErrorListener(new OnErrorListener() { - public boolean onError(MediaPlayer mp, int what, int extra) { - Log.e("Error occurred while playing audio."); - mp.stop(); - mp.release(); - mMediaPlayer = null; - return true; - } - }); - - try { - // Check if we are in a call. If we are, use the in-call alarm - // resource at a low volume to not disrupt the call. - if (mTelephonyManager.getCallState() - != TelephonyManager.CALL_STATE_IDLE) { - Log.v("Using the in-call alarm"); - mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); - setDataSourceFromResource(getResources(), mMediaPlayer, - R.raw.in_call_alarm); - } else { - mMediaPlayer.setDataSource(this, alert); - } - startAlarm(mMediaPlayer); - } catch (Exception ex) { - Log.v("Using the fallback ringtone"); - // The alert may be on the sd card which could be busy right - // now. Use the fallback ringtone. - try { - // Must reset the media player to clear the error state. - mMediaPlayer.reset(); - setDataSourceFromResource(getResources(), mMediaPlayer, - com.android.internal.R.raw.fallbackring); - startAlarm(mMediaPlayer); - } catch (Exception ex2) { - // At this point we just don't play anything. - Log.e("Failed to play fallback ringtone", ex2); - } - } - } - - /* Start the vibrator after everything is ok with the media player */ - if (alarm.vibrate) { - mVibrator.vibrate(sVibratePattern, 0); - } else { - mVibrator.cancel(); - } - - enableKiller(alarm); - mPlaying = true; - mStartTime = System.currentTimeMillis(); - } - - // Do the common stuff when starting the alarm. - private void startAlarm(MediaPlayer player) - throws java.io.IOException, IllegalArgumentException, - IllegalStateException { - player.setAudioStreamType(AudioManager.STREAM_ALARM); - player.setLooping(true); - player.prepare(); - player.start(); - } - - private void setDataSourceFromResource(Resources resources, - MediaPlayer player, int res) throws java.io.IOException { - AssetFileDescriptor afd = resources.openRawResourceFd(res); - if (afd != null) { - player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), - afd.getLength()); - afd.close(); - } - } - - /** - * Stops alarm audio and disables alarm if it not snoozed and not - * repeating - */ - public void stop() { - if (Log.LOGV) Log.v("AlarmKlaxon.stop()"); - if (mPlaying) { - mPlaying = false; - - // Stop audio playing - if (mMediaPlayer != null) { - mMediaPlayer.stop(); - mMediaPlayer.release(); - mMediaPlayer = null; - } - - // Stop vibrator - mVibrator.cancel(); - } - disableKiller(); - } - - /** - * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm - * won't run all day. - * - * This just cancels the audio, but leaves the notification - * popped, so the user will know that the alarm tripped. - */ - private void enableKiller(Alarm alarm) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER, alarm), - 1000 * ALARM_TIMEOUT_SECONDS); - } - - private void disableKiller() { - mHandler.removeMessages(KILLER); - } - - -} diff --git a/src/com/android/alarmclock/AlarmPreference.java b/src/com/android/alarmclock/AlarmPreference.java deleted file mode 100644 index cb0e3f5cd..000000000 --- a/src/com/android/alarmclock/AlarmPreference.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.alarmclock; - -import android.content.Context; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.preference.RingtonePreference; -import android.util.AttributeSet; - -/** - * The RingtonePreference does not have a way to get/set the current ringtone so - * we override onSaveRingtone and onRestoreRingtone to get the same behavior. - */ -public class AlarmPreference extends RingtonePreference { - private Uri mAlert; - - public AlarmPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onSaveRingtone(Uri ringtoneUri) { - setAlert(ringtoneUri); - } - - @Override - protected Uri onRestoreRingtone() { - return mAlert; - } - - public void setAlert(Uri alert) { - mAlert = alert; - if (alert != null) { - final Ringtone r = RingtoneManager.getRingtone(getContext(), alert); - if (r != null) { - setSummary(r.getTitle(getContext())); - } - } else { - setSummary(R.string.silent_alarm_summary); - } - } - - public String getAlertString() { - if (mAlert != null) { - return mAlert.toString(); - } - return Alarms.ALARM_ALERT_SILENT; - } -} diff --git a/src/com/android/alarmclock/AlarmProvider.java b/src/com/android/alarmclock/AlarmProvider.java deleted file mode 100644 index 5849a384f..000000000 --- a/src/com/android/alarmclock/AlarmProvider.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.text.TextUtils; - -public class AlarmProvider extends ContentProvider { - private SQLiteOpenHelper mOpenHelper; - - private static final int ALARMS = 1; - private static final int ALARMS_ID = 2; - private static final UriMatcher sURLMatcher = new UriMatcher( - UriMatcher.NO_MATCH); - - static { - sURLMatcher.addURI("com.android.alarmclock", "alarm", ALARMS); - sURLMatcher.addURI("com.android.alarmclock", "alarm/#", ALARMS_ID); - } - - private static class DatabaseHelper extends SQLiteOpenHelper { - private static final String DATABASE_NAME = "alarms.db"; - private static final int DATABASE_VERSION = 5; - - public DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE alarms (" + - "_id INTEGER PRIMARY KEY," + - "hour INTEGER, " + - "minutes INTEGER, " + - "daysofweek INTEGER, " + - "alarmtime INTEGER, " + - "enabled INTEGER, " + - "vibrate INTEGER, " + - "message TEXT, " + - "alert TEXT);"); - - // insert default alarms - String insertMe = "INSERT INTO alarms " + - "(hour, minutes, daysofweek, alarmtime, enabled, vibrate, message, alert) " + - "VALUES "; - db.execSQL(insertMe + "(7, 0, 127, 0, 0, 1, '', '');"); - db.execSQL(insertMe + "(8, 30, 31, 0, 0, 1, '', '');"); - db.execSQL(insertMe + "(9, 00, 0, 0, 0, 1, '', '');"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { - if (Log.LOGV) Log.v( - "Upgrading alarms database from version " + - oldVersion + " to " + currentVersion + - ", which will destroy all old data"); - db.execSQL("DROP TABLE IF EXISTS alarms"); - onCreate(db); - } - } - - public AlarmProvider() { - } - - @Override - public boolean onCreate() { - mOpenHelper = new DatabaseHelper(getContext()); - return true; - } - - @Override - public Cursor query(Uri url, String[] projectionIn, String selection, - String[] selectionArgs, String sort) { - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - - // Generate the body of the query - int match = sURLMatcher.match(url); - switch (match) { - case ALARMS: - qb.setTables("alarms"); - break; - case ALARMS_ID: - qb.setTables("alarms"); - qb.appendWhere("_id="); - qb.appendWhere(url.getPathSegments().get(1)); - break; - default: - throw new IllegalArgumentException("Unknown URL " + url); - } - - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, - null, null, sort); - - if (ret == null) { - if (Log.LOGV) Log.v("Alarms.query: failed"); - } else { - ret.setNotificationUri(getContext().getContentResolver(), url); - } - - return ret; - } - - @Override - public String getType(Uri url) { - int match = sURLMatcher.match(url); - switch (match) { - case ALARMS: - return "vnd.android.cursor.dir/alarms"; - case ALARMS_ID: - return "vnd.android.cursor.item/alarms"; - default: - throw new IllegalArgumentException("Unknown URL"); - } - } - - @Override - public int update(Uri url, ContentValues values, String where, String[] whereArgs) { - int count; - long rowId = 0; - int match = sURLMatcher.match(url); - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - switch (match) { - case ALARMS_ID: { - String segment = url.getPathSegments().get(1); - rowId = Long.parseLong(segment); - count = db.update("alarms", values, "_id=" + rowId, null); - break; - } - default: { - throw new UnsupportedOperationException( - "Cannot update URL: " + url); - } - } - if (Log.LOGV) Log.v("*** notifyChange() rowId: " + rowId + " url " + url); - getContext().getContentResolver().notifyChange(url, null); - return count; - } - - @Override - public Uri insert(Uri url, ContentValues initialValues) { - if (sURLMatcher.match(url) != ALARMS) { - throw new IllegalArgumentException("Cannot insert into URL: " + url); - } - - ContentValues values; - if (initialValues != null) - values = new ContentValues(initialValues); - else - values = new ContentValues(); - - if (!values.containsKey(Alarm.Columns.HOUR)) - values.put(Alarm.Columns.HOUR, 0); - - if (!values.containsKey(Alarm.Columns.MINUTES)) - values.put(Alarm.Columns.MINUTES, 0); - - if (!values.containsKey(Alarm.Columns.DAYS_OF_WEEK)) - values.put(Alarm.Columns.DAYS_OF_WEEK, 0); - - if (!values.containsKey(Alarm.Columns.ALARM_TIME)) - values.put(Alarm.Columns.ALARM_TIME, 0); - - if (!values.containsKey(Alarm.Columns.ENABLED)) - values.put(Alarm.Columns.ENABLED, 0); - - if (!values.containsKey(Alarm.Columns.VIBRATE)) - values.put(Alarm.Columns.VIBRATE, 1); - - if (!values.containsKey(Alarm.Columns.MESSAGE)) - values.put(Alarm.Columns.MESSAGE, ""); - - if (!values.containsKey(Alarm.Columns.ALERT)) - values.put(Alarm.Columns.ALERT, ""); - - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - long rowId = db.insert("alarms", Alarm.Columns.MESSAGE, values); - if (rowId < 0) { - throw new SQLException("Failed to insert row into " + url); - } - if (Log.LOGV) Log.v("Added alarm rowId = " + rowId); - - Uri newUrl = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, rowId); - getContext().getContentResolver().notifyChange(newUrl, null); - return newUrl; - } - - public int delete(Uri url, String where, String[] whereArgs) { - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - int count; - long rowId = 0; - switch (sURLMatcher.match(url)) { - case ALARMS: - count = db.delete("alarms", where, whereArgs); - break; - case ALARMS_ID: - String segment = url.getPathSegments().get(1); - rowId = Long.parseLong(segment); - if (TextUtils.isEmpty(where)) { - where = "_id=" + segment; - } else { - where = "_id=" + segment + " AND (" + where + ")"; - } - count = db.delete("alarms", where, whereArgs); - break; - default: - throw new IllegalArgumentException("Cannot delete from URL: " + url); - } - - getContext().getContentResolver().notifyChange(url, null); - return count; - } -} diff --git a/src/com/android/alarmclock/AlarmReceiver.java b/src/com/android/alarmclock/AlarmReceiver.java deleted file mode 100644 index 97374ef5c..000000000 --- a/src/com/android/alarmclock/AlarmReceiver.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.KeyguardManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; -import android.database.Cursor; -import android.os.Parcel; - -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert - * activity. Passes through Alarm ID. - */ -public class AlarmReceiver extends BroadcastReceiver { - - /** If the alarm is older than STALE_WINDOW seconds, ignore. It - is probably the result of a time or timezone change */ - private final static int STALE_WINDOW = 60 * 30; - - @Override - public void onReceive(Context context, Intent intent) { - // Take care of the easy intents first. - if (Alarms.CLEAR_NOTIFICATION.equals(intent.getAction())) { - // If this is the "Clear All Notifications" intent, stop the alarm - // service and return. - context.stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); - return; - } else if (Alarms.ALARM_KILLED.equals(intent.getAction())) { - // The alarm has been killed, update the notification - updateNotification(context, (Alarm) - intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA), - intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1)); - return; - } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) { - Alarms.saveSnoozeAlert(context, -1, -1); - return; - } - - Alarm alarm = null; - // Grab the alarm from the intent. Since the remote AlarmManagerService - // fills in the Intent to add some extra data, it must unparcel the - // Alarm object. It throws a ClassNotFoundException when unparcelling. - // To avoid this, do the marshalling ourselves. - final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA); - if (data != null) { - Parcel in = Parcel.obtain(); - in.unmarshall(data, 0, data.length); - in.setDataPosition(0); - alarm = Alarm.CREATOR.createFromParcel(in); - } - - if (alarm == null) { - Log.v("AlarmReceiver failed to parse the alarm from the intent"); - return; - } - - // Intentionally verbose: always log the alarm time to provide useful - // information in bug reports. - long now = System.currentTimeMillis(); - SimpleDateFormat format = - new SimpleDateFormat("HH:mm:ss.SSS aaa"); - Log.v("AlarmReceiver.onReceive() id " + alarm.id + " setFor " - + format.format(new Date(alarm.time))); - - if (now > alarm.time + STALE_WINDOW * 1000) { - if (Log.LOGV) { - Log.v("AlarmReceiver ignoring stale alarm"); - } - return; - } - - // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can - // pick it up. - AlarmAlertWakeLock.acquireCpuWakeLock(context); - - /* Close dialogs and window shade */ - Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.sendBroadcast(closeDialogs); - - // Decide which activity to start based on the state of the keyguard. - Class c = AlarmAlert.class; - KeyguardManager km = (KeyguardManager) context.getSystemService( - Context.KEYGUARD_SERVICE); - if (km.inKeyguardRestrictedInputMode()) { - // Use the full screen activity for security. - c = AlarmAlertFullScreen.class; - } - - /* launch UI, explicitly stating that this is not due to user action - * so that the current app's notification management is not disturbed */ - Intent alarmAlert = new Intent(context, c); - alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); - alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_NO_USER_ACTION); - context.startActivity(alarmAlert); - - // Disable the snooze alert if this alarm is the snooze. - Alarms.disableSnoozeAlert(context, alarm.id); - // Disable this alarm if it does not repeat. - if (!alarm.daysOfWeek.isRepeatSet()) { - Alarms.enableAlarm(context, alarm.id, false); - } else { - // Enable the next alert if there is one. The above call to - // enableAlarm will call setNextAlert so avoid calling it twice. - Alarms.setNextAlert(context); - } - - // Play the alarm alert and vibrate the device. - Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION); - playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); - context.startService(playAlarm); - - // Trigger a notification that, when clicked, will show the alarm alert - // dialog. No need to check for fullscreen since this will always be - // launched from a user action. - Intent notify = new Intent(context, AlarmAlert.class); - notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); - PendingIntent pendingNotify = PendingIntent.getActivity(context, - alarm.id, notify, 0); - - // Use the alarm's label or the default label as the ticker text and - // main text of the notification. - String label = alarm.getLabelOrDefault(context); - Notification n = new Notification(R.drawable.stat_notify_alarm, - label, alarm.time); - n.setLatestEventInfo(context, label, - context.getString(R.string.alarm_notify_text), - pendingNotify); - n.flags |= Notification.FLAG_SHOW_LIGHTS; - n.ledARGB = 0xFF00FF00; - n.ledOnMS = 500; - n.ledOffMS = 500; - - // Set the deleteIntent for when the user clicks "Clear All - // Notifications" - Intent clearAll = new Intent(context, AlarmReceiver.class); - clearAll.setAction(Alarms.CLEAR_NOTIFICATION); - n.deleteIntent = PendingIntent.getBroadcast(context, 0, clearAll, 0); - - // Send the notification using the alarm id to easily identify the - // correct notification. - NotificationManager nm = getNotificationManager(context); - nm.notify(alarm.id, n); - } - - private NotificationManager getNotificationManager(Context context) { - return (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - } - - private void updateNotification(Context context, Alarm alarm, int timeout) { - NotificationManager nm = getNotificationManager(context); - - // If the alarm is null, just cancel the notification. - if (alarm == null) { - if (Log.LOGV) { - Log.v("Cannot update notification for killer callback"); - } - return; - } - - // Launch SetAlarm when clicked. - Intent viewAlarm = new Intent(context, SetAlarm.class); - viewAlarm.putExtra(Alarms.ALARM_ID, alarm.id); - PendingIntent intent = - PendingIntent.getActivity(context, alarm.id, viewAlarm, 0); - - // Update the notification to indicate that the alert has been - // silenced. - String label = alarm.getLabelOrDefault(context); - Notification n = new Notification(R.drawable.stat_notify_alarm, - label, alarm.time); - n.setLatestEventInfo(context, label, - context.getString(R.string.alarm_alert_alert_silenced, timeout), - intent); - n.flags |= Notification.FLAG_AUTO_CANCEL; - nm.notify(alarm.id, n); - } -} diff --git a/src/com/android/alarmclock/Alarms.java b/src/com/android/alarmclock/Alarms.java deleted file mode 100644 index 63a67d776..000000000 --- a/src/com/android/alarmclock/Alarms.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.Uri; -import android.os.Parcel; -import android.provider.Settings; -import android.text.format.DateFormat; - -import java.util.Calendar; -import java.text.DateFormatSymbols; - -/** - * The Alarms provider supplies info about Alarm Clock settings - */ -public class Alarms { - - // This action triggers the AlarmReceiver as well as the AlarmKlaxon. It - // is a public action used in the manifest for receiving Alarm broadcasts - // from the alarm manager. - public static final String ALARM_ALERT_ACTION = "com.android.alarmclock.ALARM_ALERT"; - - // This is a private action used when the user clears all notifications. - public static final String CLEAR_NOTIFICATION = "clear_notification"; - - // This is a private action used by the AlarmKlaxon to update the UI to - // show the alarm has been killed. - public static final String ALARM_KILLED = "alarm_killed"; - - // Extra in the ALARM_KILLED intent to indicate to the user how long the - // alarm played before being killed. - public static final String ALARM_KILLED_TIMEOUT = "alarm_killed_timeout"; - - // This string is used to indicate a silent alarm in the db. - public static final String ALARM_ALERT_SILENT = "silent"; - - // This intent is sent from the notification when the user cancels the - // snooze alert. - public static final String CANCEL_SNOOZE = "cancel_snooze"; - - // This string is used when passing an Alarm object through an intent. - public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm"; - - // This extra is the raw Alarm object data. It is used in the - // AlarmManagerService to avoid a ClassNotFoundException when filling in - // the Intent extras. - public static final String ALARM_RAW_DATA = "intent.extra.alarm_raw"; - - // This string is used to identify the alarm id passed to SetAlarm from the - // list of alarms. - public static final String ALARM_ID = "alarm_id"; - - final static String PREF_SNOOZE_ID = "snooze_id"; - final static String PREF_SNOOZE_TIME = "snooze_time"; - - private final static String DM12 = "E h:mm aa"; - private final static String DM24 = "E k:mm"; - - private final static String M12 = "h:mm aa"; - // Shared with DigitalClock - final static String M24 = "kk:mm"; - - /** - * Creates a new Alarm. - */ - public static Uri addAlarm(ContentResolver contentResolver) { - ContentValues values = new ContentValues(); - values.put(Alarm.Columns.HOUR, 8); - return contentResolver.insert(Alarm.Columns.CONTENT_URI, values); - } - - /** - * Removes an existing Alarm. If this alarm is snoozing, disables - * snooze. Sets next alert. - */ - public static void deleteAlarm( - Context context, int alarmId) { - - ContentResolver contentResolver = context.getContentResolver(); - /* If alarm is snoozing, lose it */ - disableSnoozeAlert(context, alarmId); - - Uri uri = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId); - contentResolver.delete(uri, "", null); - - setNextAlert(context); - } - - /** - * Queries all alarms - * @return cursor over all alarms - */ - public static Cursor getAlarmsCursor(ContentResolver contentResolver) { - return contentResolver.query( - Alarm.Columns.CONTENT_URI, Alarm.Columns.ALARM_QUERY_COLUMNS, - null, null, Alarm.Columns.DEFAULT_SORT_ORDER); - } - - // Private method to get a more limited set of alarms from the database. - private static Cursor getFilteredAlarmsCursor( - ContentResolver contentResolver) { - return contentResolver.query(Alarm.Columns.CONTENT_URI, - Alarm.Columns.ALARM_QUERY_COLUMNS, Alarm.Columns.WHERE_ENABLED, - null, null); - } - - /** - * Return an Alarm object representing the alarm id in the database. - * Returns null if no alarm exists. - */ - public static Alarm getAlarm(ContentResolver contentResolver, int alarmId) { - Cursor cursor = contentResolver.query( - ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId), - Alarm.Columns.ALARM_QUERY_COLUMNS, - null, null, null); - Alarm alarm = null; - if (cursor != null) { - if (cursor.moveToFirst()) { - alarm = new Alarm(cursor); - } - cursor.close(); - } - return alarm; - } - - - /** - * A convenience method to set an alarm in the Alarms - * content provider. - * - * @param id corresponds to the _id column - * @param enabled corresponds to the ENABLED column - * @param hour corresponds to the HOUR column - * @param minutes corresponds to the MINUTES column - * @param daysOfWeek corresponds to the DAYS_OF_WEEK column - * @param time corresponds to the ALARM_TIME column - * @param vibrate corresponds to the VIBRATE column - * @param message corresponds to the MESSAGE column - * @param alert corresponds to the ALERT column - */ - public static void setAlarm( - Context context, int id, boolean enabled, int hour, int minutes, - Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message, - String alert) { - - ContentValues values = new ContentValues(8); - ContentResolver resolver = context.getContentResolver(); - // Set the alarm_time value if this alarm does not repeat. This will be - // used later to disable expired alarms. - long time = 0; - if (!daysOfWeek.isRepeatSet()) { - time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis(); - } - - if (Log.LOGV) Log.v( - "** setAlarm * idx " + id + " hour " + hour + " minutes " + - minutes + " enabled " + enabled + " time " + time); - - values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0); - values.put(Alarm.Columns.HOUR, hour); - values.put(Alarm.Columns.MINUTES, minutes); - values.put(Alarm.Columns.ALARM_TIME, time); - values.put(Alarm.Columns.DAYS_OF_WEEK, daysOfWeek.getCoded()); - values.put(Alarm.Columns.VIBRATE, vibrate); - values.put(Alarm.Columns.MESSAGE, message); - values.put(Alarm.Columns.ALERT, alert); - resolver.update(ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, id), - values, null, null); - - setNextAlert(context); - } - - /** - * A convenience method to enable or disable an alarm. - * - * @param id corresponds to the _id column - * @param enabled corresponds to the ENABLED column - */ - - public static void enableAlarm( - final Context context, final int id, boolean enabled) { - enableAlarmInternal(context, id, enabled); - setNextAlert(context); - } - - private static void enableAlarmInternal(final Context context, - final int id, boolean enabled) { - enableAlarmInternal(context, getAlarm(context.getContentResolver(), id), - enabled); - } - - private static void enableAlarmInternal(final Context context, - final Alarm alarm, boolean enabled) { - ContentResolver resolver = context.getContentResolver(); - - ContentValues values = new ContentValues(2); - values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0); - - // If we are enabling the alarm, calculate alarm time since the time - // value in Alarm may be old. - if (enabled) { - long time = 0; - if (!alarm.daysOfWeek.isRepeatSet()) { - time = calculateAlarm(alarm.hour, alarm.minutes, - alarm.daysOfWeek).getTimeInMillis(); - } - values.put(Alarm.Columns.ALARM_TIME, time); - } - - resolver.update(ContentUris.withAppendedId( - Alarm.Columns.CONTENT_URI, alarm.id), values, null, null); - } - - public static Alarm calculateNextAlert(final Context context) { - Alarm alarm = null; - long minTime = Long.MAX_VALUE; - long now = System.currentTimeMillis(); - Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver()); - if (cursor != null) { - if (cursor.moveToFirst()) { - do { - Alarm a = new Alarm(cursor); - // A time of 0 indicates this is a repeating alarm, so - // calculate the time to get the next alert. - if (a.time == 0) { - a.time = calculateAlarm(a.hour, a.minutes, a.daysOfWeek) - .getTimeInMillis(); - } else if (a.time < now) { - // Expired alarm, disable it and move along. - enableAlarmInternal(context, a, false); - continue; - } - if (a.time < minTime) { - minTime = a.time; - alarm = a; - } - } while (cursor.moveToNext()); - } - cursor.close(); - } - return alarm; - } - - /** - * Disables non-repeating alarms that have passed. Called at - * boot. - */ - public static void disableExpiredAlarms(final Context context) { - Cursor cur = getFilteredAlarmsCursor(context.getContentResolver()); - long now = System.currentTimeMillis(); - - if (cur.moveToFirst()) { - do { - Alarm alarm = new Alarm(cur); - // A time of 0 means this alarm repeats. If the time is - // non-zero, check if the time is before now. - if (alarm.time != 0 && alarm.time < now) { - if (Log.LOGV) { - Log.v("** DISABLE " + alarm.id + " now " + now +" set " - + alarm.time); - } - enableAlarmInternal(context, alarm, false); - } - } while (cur.moveToNext()); - } - cur.close(); - } - - /** - * Called at system startup, on time/timezone change, and whenever - * the user changes alarm settings. Activates snooze if set, - * otherwise loads all alarms, activates next alert. - */ - public static void setNextAlert(final Context context) { - if (!enableSnoozeAlert(context)) { - Alarm alarm = calculateNextAlert(context); - if (alarm != null) { - enableAlert(context, alarm, alarm.time); - } else { - disableAlert(context); - } - } - } - - /** - * Sets alert in AlarmManger and StatusBar. This is what will - * actually launch the alert when the alarm triggers. - * - * @param alarm Alarm. - * @param atTimeInMillis milliseconds since epoch - */ - private static void enableAlert(Context context, final Alarm alarm, - final long atTimeInMillis) { - AlarmManager am = (AlarmManager) - context.getSystemService(Context.ALARM_SERVICE); - - if (Log.LOGV) { - Log.v("** setAlert id " + alarm.id + " atTime " + atTimeInMillis); - } - - Intent intent = new Intent(ALARM_ALERT_ACTION); - - // XXX: This is a slight hack to avoid an exception in the remote - // AlarmManagerService process. The AlarmManager adds extra data to - // this Intent which causes it to inflate. Since the remote process - // does not know about the Alarm class, it throws a - // ClassNotFoundException. - // - // To avoid this, we marshall the data ourselves and then parcel a plain - // byte[] array. The AlarmReceiver class knows to build the Alarm - // object from the byte[] array. - Parcel out = Parcel.obtain(); - alarm.writeToParcel(out, 0); - out.setDataPosition(0); - intent.putExtra(ALARM_RAW_DATA, out.marshall()); - - PendingIntent sender = PendingIntent.getBroadcast( - context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - - am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); - - setStatusBarIcon(context, true); - - Calendar c = Calendar.getInstance(); - c.setTime(new java.util.Date(atTimeInMillis)); - String timeString = formatDayAndTime(context, c); - saveNextAlarm(context, timeString); - } - - /** - * Disables alert in AlarmManger and StatusBar. - * - * @param id Alarm ID. - */ - static void disableAlert(Context context) { - AlarmManager am = (AlarmManager) - context.getSystemService(Context.ALARM_SERVICE); - PendingIntent sender = PendingIntent.getBroadcast( - context, 0, new Intent(ALARM_ALERT_ACTION), - PendingIntent.FLAG_CANCEL_CURRENT); - am.cancel(sender); - setStatusBarIcon(context, false); - saveNextAlarm(context, ""); - } - - static void saveSnoozeAlert(final Context context, final int id, - final long time) { - SharedPreferences prefs = context.getSharedPreferences( - AlarmClock.PREFERENCES, 0); - SharedPreferences.Editor ed = prefs.edit(); - if (id == -1) { - clearSnoozePreference(ed); - } else { - ed.putInt(PREF_SNOOZE_ID, id); - ed.putLong(PREF_SNOOZE_TIME, time); - ed.commit(); - } - // Set the next alert after updating the snooze. - setNextAlert(context); - } - - /** - * Disable the snooze alert if the given id matches the snooze id. - */ - static void disableSnoozeAlert(final Context context, final int id) { - SharedPreferences prefs = context.getSharedPreferences( - AlarmClock.PREFERENCES, 0); - int snoozeId = prefs.getInt(PREF_SNOOZE_ID, -1); - if (snoozeId == -1) { - // No snooze set, do nothing. - return; - } else if (snoozeId == id) { - // This is the same id so clear the shared prefs. - clearSnoozePreference(prefs.edit()); - } - } - - // Helper to remove the snooze preference. Do not use clear because that - // will erase the clock preferences. - private static void clearSnoozePreference(final SharedPreferences.Editor ed) { - ed.remove(PREF_SNOOZE_ID); - ed.remove(PREF_SNOOZE_TIME); - ed.commit(); - }; - - /** - * If there is a snooze set, enable it in AlarmManager - * @return true if snooze is set - */ - private static boolean enableSnoozeAlert(final Context context) { - SharedPreferences prefs = context.getSharedPreferences( - AlarmClock.PREFERENCES, 0); - - int id = prefs.getInt(PREF_SNOOZE_ID, -1); - if (id == -1) { - return false; - } - long time = prefs.getLong(PREF_SNOOZE_TIME, -1); - - // Get the alarm from the db. - final Alarm alarm = getAlarm(context.getContentResolver(), id); - // The time in the database is either 0 (repeating) or a specific time - // for a non-repeating alarm. Update this value so the AlarmReceiver - // has the right time to compare. - alarm.time = time; - - enableAlert(context, alarm, time); - return true; - } - - /** - * Tells the StatusBar whether the alarm is enabled or disabled - */ - private static void setStatusBarIcon(Context context, boolean enabled) { - Intent alarmChanged = new Intent(Intent.ACTION_ALARM_CHANGED); - alarmChanged.putExtra("alarmSet", enabled); - context.sendBroadcast(alarmChanged); - } - - /** - * Given an alarm in hours and minutes, return a time suitable for - * setting in AlarmManager. - * @param hour Always in 24 hour 0-23 - * @param minute 0-59 - * @param daysOfWeek 0-59 - */ - static Calendar calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek) { - - // start with now - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(System.currentTimeMillis()); - - int nowHour = c.get(Calendar.HOUR_OF_DAY); - int nowMinute = c.get(Calendar.MINUTE); - - // if alarm is behind current time, advance one day - if (hour < nowHour || - hour == nowHour && minute <= nowMinute) { - c.add(Calendar.DAY_OF_YEAR, 1); - } - c.set(Calendar.HOUR_OF_DAY, hour); - c.set(Calendar.MINUTE, minute); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - - int addDays = daysOfWeek.getNextAlarm(c); - /* Log.v("** TIMES * " + c.getTimeInMillis() + " hour " + hour + - " minute " + minute + " dow " + c.get(Calendar.DAY_OF_WEEK) + " from now " + - addDays); */ - if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays); - return c; - } - - static String formatTime(final Context context, int hour, int minute, - Alarm.DaysOfWeek daysOfWeek) { - Calendar c = calculateAlarm(hour, minute, daysOfWeek); - return formatTime(context, c); - } - - /* used by AlarmAlert */ - static String formatTime(final Context context, Calendar c) { - String format = get24HourMode(context) ? M24 : M12; - return (c == null) ? "" : (String)DateFormat.format(format, c); - } - - /** - * Shows day and time -- used for lock screen - */ - private static String formatDayAndTime(final Context context, Calendar c) { - String format = get24HourMode(context) ? DM24 : DM12; - return (c == null) ? "" : (String)DateFormat.format(format, c); - } - - /** - * Save time of the next alarm, as a formatted string, into the system - * settings so those who care can make use of it. - */ - static void saveNextAlarm(final Context context, String timeString) { - Settings.System.putString(context.getContentResolver(), - Settings.System.NEXT_ALARM_FORMATTED, - timeString); - } - - /** - * @return true if clock is set to 24-hour mode - */ - static boolean get24HourMode(final Context context) { - return android.text.format.DateFormat.is24HourFormat(context); - } -} diff --git a/src/com/android/alarmclock/AnalogAppWidgetProvider.java b/src/com/android/alarmclock/AnalogAppWidgetProvider.java deleted file mode 100644 index 524b1d22c..000000000 --- a/src/com/android/alarmclock/AnalogAppWidgetProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2009 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.alarmclock; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.PorterDuff; -import android.net.Uri; -import android.provider.Calendar; -import android.provider.Calendar.Attendees; -import android.provider.Calendar.Calendars; -import android.provider.Calendar.EventsColumns; -import android.provider.Calendar.Instances; -import android.provider.Calendar.Reminders; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.util.Config; -import android.util.Log; -import android.view.View; -import android.widget.RemoteViews; - -import java.util.Arrays; - -/** - * Simple widget to show analog clock. - */ -public class AnalogAppWidgetProvider extends BroadcastReceiver { - static final String TAG = "AnalogAppWidgetProvider"; - - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { - RemoteViews views = new RemoteViews(context.getPackageName(), - R.layout.analog_appwidget); - - int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); - - AppWidgetManager gm = AppWidgetManager.getInstance(context); - gm.updateAppWidget(appWidgetIds, views); - } - } -} - diff --git a/src/com/android/alarmclock/ClockPicker.java b/src/com/android/alarmclock/ClockPicker.java deleted file mode 100644 index 31150a2d5..000000000 --- a/src/com/android/alarmclock/ClockPicker.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.alarmclock; - -import android.app.Activity; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Gallery; - -/** - * Clock face picker for the Alarm Clock application. - */ -public class ClockPicker extends Activity implements - AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener { - - private LayoutInflater mFactory; - private Gallery mGallery; - - private SharedPreferences mPrefs; - private View mClock; - private ViewGroup mClockLayout; - private int mPosition; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - requestWindowFeature(Window.FEATURE_NO_TITLE); - - mFactory = LayoutInflater.from(this); - setContentView(R.layout.clockpicker); - - mGallery = (Gallery) findViewById(R.id.gallery); - mGallery.setAdapter(new ClockAdapter()); - mGallery.setOnItemSelectedListener(this); - mGallery.setOnItemClickListener(this); - - mPrefs = getSharedPreferences(AlarmClock.PREFERENCES, 0); - int face = mPrefs.getInt(AlarmClock.PREF_CLOCK_FACE, 0); - if (face < 0 || face >= AlarmClock.CLOCKS.length) face = 0; - - mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); - mClockLayout.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - selectClock(mPosition); - } - }); - - mGallery.setSelection(face, false); - } - - public void onItemSelected(AdapterView parent, View v, int position, long id) { - if (mClock != null) { - mClockLayout.removeView(mClock); - } - mClock = mFactory.inflate(AlarmClock.CLOCKS[position], null); - mClockLayout.addView(mClock, 0); - mPosition = position; - } - - public void onItemClick(AdapterView parent, View v, int position, long id) { - selectClock(position); - } - - private synchronized void selectClock(int position) { - SharedPreferences.Editor ed = mPrefs.edit(); - ed.putInt(AlarmClock.PREF_CLOCK_FACE, position); - ed.commit(); - - setResult(RESULT_OK); - finish(); - } - - public void onNothingSelected(AdapterView parent) { - } - - class ClockAdapter extends BaseAdapter { - - public ClockAdapter() { - } - - public int getCount() { - return AlarmClock.CLOCKS.length; - } - - public Object getItem(int position) { - return position; - } - - public long getItemId(int position) { - return position; - } - - public View getView(final int position, View convertView, ViewGroup parent) { - View clock = mFactory.inflate(AlarmClock.CLOCKS[position], null); - return clock; - } - - } -} diff --git a/src/com/android/alarmclock/DigitalClock.java b/src/com/android/alarmclock/DigitalClock.java deleted file mode 100644 index 7ae92f185..000000000 --- a/src/com/android/alarmclock/DigitalClock.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * 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.alarmclock; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.drawable.AnimationDrawable; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.provider.Settings; -import android.text.format.DateFormat; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.Calendar; - -/** - * Displays the time - */ -public class DigitalClock extends LinearLayout { - - private final static String M12 = "h:mm"; - - private Calendar mCalendar; - private String mFormat; - private TextView mTimeDisplay; - private AmPm mAmPm; - private boolean mAnimate; - private ContentObserver mFormatChangeObserver; - private boolean mLive = true; - private boolean mAttached; - - /* called by system on minute ticks */ - private final Handler mHandler = new Handler(); - private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mLive && intent.getAction().equals( - Intent.ACTION_TIMEZONE_CHANGED)) { - mCalendar = Calendar.getInstance(); - } - updateTime(); - } - }; - - static class AmPm { - private int mColorOn, mColorOff; - - private LinearLayout mAmPmLayout; - private TextView mAm, mPm; - - AmPm(View parent) { - mAmPmLayout = (LinearLayout) parent.findViewById(R.id.am_pm); - mAm = (TextView)mAmPmLayout.findViewById(R.id.am); - mPm = (TextView)mAmPmLayout.findViewById(R.id.pm); - - Resources r = parent.getResources(); - mColorOn = r.getColor(R.color.ampm_on); - mColorOff = r.getColor(R.color.ampm_off); - } - - void setShowAmPm(boolean show) { - mAmPmLayout.setVisibility(show ? View.VISIBLE : View.GONE); - } - - void setIsMorning(boolean isMorning) { - mAm.setTextColor(isMorning ? mColorOn : mColorOff); - mPm.setTextColor(isMorning ? mColorOff : mColorOn); - } - } - - private class FormatChangeObserver extends ContentObserver { - public FormatChangeObserver() { - super(new Handler()); - } - @Override - public void onChange(boolean selfChange) { - setDateFormat(); - updateTime(); - } - } - - public DigitalClock(Context context) { - this(context, null); - } - - public DigitalClock(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mTimeDisplay = (TextView) findViewById(R.id.timeDisplay); - mAmPm = new AmPm(this); - mCalendar = Calendar.getInstance(); - - setDateFormat(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (Log.LOGV) Log.v("onAttachedToWindow " + this); - - if (mAttached) return; - mAttached = true; - - if (mAnimate) { - setBackgroundResource(R.drawable.animate_circle); - /* Start the animation (looped playback by default). */ - ((AnimationDrawable) getBackground()).start(); - } - - if (mLive) { - /* monitor time ticks, time changed, timezone */ - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_TIME_TICK); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); - } - - /* monitor 12/24-hour display preference */ - mFormatChangeObserver = new FormatChangeObserver(); - mContext.getContentResolver().registerContentObserver( - Settings.System.CONTENT_URI, true, mFormatChangeObserver); - - updateTime(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - if (!mAttached) return; - mAttached = false; - - Drawable background = getBackground(); - if (background instanceof AnimationDrawable) { - ((AnimationDrawable) background).stop(); - } - - if (mLive) { - mContext.unregisterReceiver(mIntentReceiver); - } - mContext.getContentResolver().unregisterContentObserver( - mFormatChangeObserver); - } - - - void updateTime(Calendar c) { - mCalendar = c; - updateTime(); - } - - private void updateTime() { - if (mLive) { - mCalendar.setTimeInMillis(System.currentTimeMillis()); - } - - CharSequence newTime = DateFormat.format(mFormat, mCalendar); - mTimeDisplay.setText(newTime); - mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0); - } - - private void setDateFormat() { - mFormat = Alarms.get24HourMode(mContext) ? Alarms.M24 : M12; - mAmPm.setShowAmPm(mFormat == M12); - } - - void setAnimate() { - mAnimate = true; - } - - void setLive(boolean live) { - mLive = live; - } -} diff --git a/src/com/android/alarmclock/Log.java b/src/com/android/alarmclock/Log.java deleted file mode 100644 index 18cc391d0..000000000 --- a/src/com/android/alarmclock/Log.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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-level logging flag - */ - -package com.android.alarmclock; - -import android.os.SystemClock; -import android.util.Config; - -class Log { - public final static String LOGTAG = "AlarmClock"; - - static final boolean LOGV = AlarmClock.DEBUG ? Config.LOGD : Config.LOGV; - - static void v(String logMe) { - android.util.Log.v(LOGTAG, /* SystemClock.uptimeMillis() + " " + */ logMe); - } - - static void e(String logMe) { - android.util.Log.e(LOGTAG, logMe); - } - - static void e(String logMe, Exception ex) { - android.util.Log.e(LOGTAG, logMe, ex); - } -} diff --git a/src/com/android/alarmclock/RepeatPreference.java b/src/com/android/alarmclock/RepeatPreference.java deleted file mode 100644 index 6af023ba4..000000000 --- a/src/com/android/alarmclock/RepeatPreference.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.alarmclock; - -import android.app.AlertDialog.Builder; -import android.content.Context; -import android.content.DialogInterface; -import android.preference.ListPreference; -import android.util.AttributeSet; - -import java.text.DateFormatSymbols; -import java.util.Calendar; - -public class RepeatPreference extends ListPreference { - - // Initial value that can be set with the values saved in the database. - private Alarm.DaysOfWeek mDaysOfWeek = new Alarm.DaysOfWeek(0); - // New value that will be set if a positive result comes back from the - // dialog. - private Alarm.DaysOfWeek mNewDaysOfWeek = new Alarm.DaysOfWeek(0); - - public RepeatPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - String[] weekdays = new DateFormatSymbols().getWeekdays(); - String[] values = new String[] { - weekdays[Calendar.MONDAY], - weekdays[Calendar.TUESDAY], - weekdays[Calendar.WEDNESDAY], - weekdays[Calendar.THURSDAY], - weekdays[Calendar.FRIDAY], - weekdays[Calendar.SATURDAY], - weekdays[Calendar.SUNDAY], - }; - setEntries(values); - setEntryValues(values); - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - if (positiveResult) { - mDaysOfWeek.set(mNewDaysOfWeek); - setSummary(mDaysOfWeek.toString(getContext(), true)); - } - } - - @Override - protected void onPrepareDialogBuilder(Builder builder) { - CharSequence[] entries = getEntries(); - CharSequence[] entryValues = getEntryValues(); - - builder.setMultiChoiceItems( - entries, mDaysOfWeek.getBooleanArray(), - new DialogInterface.OnMultiChoiceClickListener() { - public void onClick(DialogInterface dialog, int which, - boolean isChecked) { - mNewDaysOfWeek.set(which, isChecked); - } - }); - } - - public void setDaysOfWeek(Alarm.DaysOfWeek dow) { - mDaysOfWeek.set(dow); - mNewDaysOfWeek.set(dow); - setSummary(dow.toString(getContext(), true)); - } - - public Alarm.DaysOfWeek getDaysOfWeek() { - return mDaysOfWeek; - } -} diff --git a/src/com/android/alarmclock/SetAlarm.java b/src/com/android/alarmclock/SetAlarm.java deleted file mode 100644 index 03533af8f..000000000 --- a/src/com/android/alarmclock/SetAlarm.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.alarmclock; - -import android.app.TimePickerDialog; -import android.content.Context; -import android.content.Intent; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.PreferenceActivity; -import android.preference.PreferenceScreen; -import android.text.format.DateFormat; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TimePicker; -import android.widget.Toast; - -/** - * Manages each alarm - */ -public class SetAlarm extends PreferenceActivity - implements TimePickerDialog.OnTimeSetListener { - - private EditTextPreference mLabel; - private Preference mTimePref; - private AlarmPreference mAlarmPref; - private CheckBoxPreference mVibratePref; - private RepeatPreference mRepeatPref; - private MenuItem mDeleteAlarmItem; - private MenuItem mTestAlarmItem; - - private int mId; - private boolean mEnabled; - private int mHour; - private int mMinutes; - - /** - * Set an alarm. Requires an Alarms.ALARM_ID to be passed in as an - * extra. FIXME: Pass an Alarm object like every other Activity. - */ - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - addPreferencesFromResource(R.xml.alarm_prefs); - - // Get each preference so we can retrieve the value later. - mLabel = (EditTextPreference) findPreference("label"); - mLabel.setOnPreferenceChangeListener( - new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference p, - Object newValue) { - // Set the summary based on the new label. - p.setSummary((String) newValue); - return true; - } - }); - mTimePref = findPreference("time"); - mAlarmPref = (AlarmPreference) findPreference("alarm"); - mVibratePref = (CheckBoxPreference) findPreference("vibrate"); - mRepeatPref = (RepeatPreference) findPreference("setRepeat"); - - Intent i = getIntent(); - mId = i.getIntExtra(Alarms.ALARM_ID, -1); - if (Log.LOGV) { - Log.v("In SetAlarm, alarm id = " + mId); - } - - /* load alarm details from database */ - Alarm alarm = Alarms.getAlarm(getContentResolver(), mId); - mEnabled = alarm.enabled; - mLabel.setText(alarm.label); - mLabel.setSummary(alarm.label); - mHour = alarm.hour; - mMinutes = alarm.minutes; - mRepeatPref.setDaysOfWeek(alarm.daysOfWeek); - mVibratePref.setChecked(alarm.vibrate); - // Give the alert uri to the preference. - mAlarmPref.setAlert(alarm.alert); - updateTime(); - - // We have to do this to get the save/cancel buttons to highlight on - // their own. - getListView().setItemsCanFocus(true); - - // Grab the content view so we can modify it. - FrameLayout content = (FrameLayout) getWindow().getDecorView() - .findViewById(com.android.internal.R.id.content); - - // Get the main ListView and remove it from the content view. - ListView lv = getListView(); - content.removeView(lv); - - // Create the new LinearLayout that will become the content view and - // make it vertical. - LinearLayout ll = new LinearLayout(this); - ll.setOrientation(LinearLayout.VERTICAL); - - // Have the ListView expand to fill the screen minus the save/cancel - // buttons. - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LayoutParams.FILL_PARENT, - LayoutParams.WRAP_CONTENT); - lp.weight = 1; - ll.addView(lv, lp); - - // Inflate the buttons onto the LinearLayout. - View v = LayoutInflater.from(this).inflate( - R.layout.save_cancel_alarm, ll); - - // Attach actions to each button. - Button b = (Button) v.findViewById(R.id.alarm_save); - b.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - saveAlarm(); - finish(); - } - }); - b = (Button) v.findViewById(R.id.alarm_cancel); - b.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - finish(); - } - }); - - // Replace the old content view with our new one. - setContentView(ll); - } - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, - Preference preference) { - if (preference == mTimePref) { - new TimePickerDialog(this, this, mHour, mMinutes, - DateFormat.is24HourFormat(this)).show(); - } - - return super.onPreferenceTreeClick(preferenceScreen, preference); - } - - @Override - public void onBackPressed() { - saveAlarm(); - finish(); - } - - public void onTimeSet(TimePicker view, int hourOfDay, int minute) { - mHour = hourOfDay; - mMinutes = minute; - updateTime(); - // If the time has been changed, enable the alarm. - mEnabled = true; - } - - private void updateTime() { - if (Log.LOGV) { - Log.v("updateTime " + mId); - } - mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, - mRepeatPref.getDaysOfWeek())); - } - - private void saveAlarm() { - final String alert = mAlarmPref.getAlertString(); - Alarms.setAlarm(this, mId, mEnabled, mHour, mMinutes, - mRepeatPref.getDaysOfWeek(), mVibratePref.isChecked(), - mLabel.getText(), alert); - - if (mEnabled) { - popAlarmSetToast(this, mHour, mMinutes, - mRepeatPref.getDaysOfWeek()); - } - } - - /** - * Write alarm out to persistent store and pops toast if alarm - * enabled - */ - private static void saveAlarm( - Context context, int id, boolean enabled, int hour, int minute, - Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String label, - String alert, boolean popToast) { - if (Log.LOGV) Log.v("** saveAlarm " + id + " " + label + " " + enabled - + " " + hour + " " + minute + " vibe " + vibrate); - - // Fix alert string first - Alarms.setAlarm(context, id, enabled, hour, minute, daysOfWeek, vibrate, - label, alert); - - if (enabled && popToast) { - popAlarmSetToast(context, hour, minute, daysOfWeek); - } - } - - /** - * Display a toast that tells the user how long until the alarm - * goes off. This helps prevent "am/pm" mistakes. - */ - static void popAlarmSetToast(Context context, int hour, int minute, - Alarm.DaysOfWeek daysOfWeek) { - - String toastText = formatToast(context, hour, minute, daysOfWeek); - Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); - ToastMaster.setToast(toast); - toast.show(); - } - - /** - * format "Alarm set for 2 days 7 hours and 53 minutes from - * now" - */ - static String formatToast(Context context, int hour, int minute, - Alarm.DaysOfWeek daysOfWeek) { - long alarm = Alarms.calculateAlarm(hour, minute, - daysOfWeek).getTimeInMillis(); - long delta = alarm - System.currentTimeMillis();; - long hours = delta / (1000 * 60 * 60); - long minutes = delta / (1000 * 60) % 60; - long days = hours / 24; - hours = hours % 24; - - String daySeq = (days == 0) ? "" : - (days == 1) ? context.getString(R.string.day) : - context.getString(R.string.days, Long.toString(days)); - - String minSeq = (minutes == 0) ? "" : - (minutes == 1) ? context.getString(R.string.minute) : - context.getString(R.string.minutes, Long.toString(minutes)); - - String hourSeq = (hours == 0) ? "" : - (hours == 1) ? context.getString(R.string.hour) : - context.getString(R.string.hours, Long.toString(hours)); - - boolean dispDays = days > 0; - boolean dispHour = hours > 0; - boolean dispMinute = minutes > 0; - - int index = (dispDays ? 1 : 0) | - (dispHour ? 2 : 0) | - (dispMinute ? 4 : 0); - - String[] formats = context.getResources().getStringArray(R.array.alarm_set); - return String.format(formats[index], daySeq, hourSeq, minSeq); - } - - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - - mDeleteAlarmItem = menu.add(0, 0, 0, R.string.delete_alarm); - mDeleteAlarmItem.setIcon(android.R.drawable.ic_menu_delete); - - if (AlarmClock.DEBUG) { - mTestAlarmItem = menu.add(0, 0, 0, "test alarm"); - } - - return true; - } - - public boolean onOptionsItemSelected(MenuItem item) { - if (item == mDeleteAlarmItem) { - Alarms.deleteAlarm(this, mId); - finish(); - return true; - } - if (AlarmClock.DEBUG) { - if (item == mTestAlarmItem) { - setTestAlarm(); - return true; - } - } - - return false; - } - - - /** - * Test code: this is disabled for production build. Sets - * this alarm to go off on the next minute - */ - void setTestAlarm() { - - // start with now - java.util.Calendar c = java.util.Calendar.getInstance(); - c.setTimeInMillis(System.currentTimeMillis()); - - int nowHour = c.get(java.util.Calendar.HOUR_OF_DAY); - int nowMinute = c.get(java.util.Calendar.MINUTE); - - int minutes = (nowMinute + 1) % 60; - int hour = nowHour + (nowMinute == 0 ? 1 : 0); - - saveAlarm(this, mId, true, hour, minutes, mRepeatPref.getDaysOfWeek(), - true, mLabel.getText(), mAlarmPref.getAlertString(), true); - } - -} diff --git a/src/com/android/alarmclock/SettingsActivity.java b/src/com/android/alarmclock/SettingsActivity.java deleted file mode 100644 index f0b97fd4f..000000000 --- a/src/com/android/alarmclock/SettingsActivity.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2009 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.alarmclock; - -import android.media.AudioManager; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceActivity; -import android.preference.PreferenceScreen; -import android.provider.Settings; - -/** - * Settings for the Alarm Clock. - */ -public class SettingsActivity extends PreferenceActivity - implements Preference.OnPreferenceChangeListener { - - private static final int ALARM_STREAM_TYPE_BIT = - 1 << AudioManager.STREAM_ALARM; - - private static final String KEY_ALARM_IN_SILENT_MODE = - "alarm_in_silent_mode"; - static final String KEY_ALARM_SNOOZE = - "snooze_duration"; - static final String KEY_VOLUME_BEHAVIOR = - "volume_button_setting"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.settings); - } - - @Override - protected void onResume() { - super.onResume(); - refresh(); - } - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, - Preference preference) { - if (KEY_ALARM_IN_SILENT_MODE.equals(preference.getKey())) { - CheckBoxPreference pref = (CheckBoxPreference) preference; - int ringerModeStreamTypes = Settings.System.getInt( - getContentResolver(), - Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - - if (pref.isChecked()) { - ringerModeStreamTypes &= ~ALARM_STREAM_TYPE_BIT; - } else { - ringerModeStreamTypes |= ALARM_STREAM_TYPE_BIT; - } - - Settings.System.putInt(getContentResolver(), - Settings.System.MODE_RINGER_STREAMS_AFFECTED, - ringerModeStreamTypes); - - return true; - } - - return super.onPreferenceTreeClick(preferenceScreen, preference); - } - - public boolean onPreferenceChange(Preference pref, Object newValue) { - final ListPreference listPref = (ListPreference) pref; - final int idx = listPref.findIndexOfValue((String) newValue); - listPref.setSummary(listPref.getEntries()[idx]); - return true; - } - - private void refresh() { - final CheckBoxPreference alarmInSilentModePref = - (CheckBoxPreference) findPreference(KEY_ALARM_IN_SILENT_MODE); - final int silentModeStreams = - Settings.System.getInt(getContentResolver(), - Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - alarmInSilentModePref.setChecked( - (silentModeStreams & ALARM_STREAM_TYPE_BIT) == 0); - - final ListPreference snooze = - (ListPreference) findPreference(KEY_ALARM_SNOOZE); - snooze.setSummary(snooze.getEntry()); - snooze.setOnPreferenceChangeListener(this); - } - -} diff --git a/src/com/android/alarmclock/ToastMaster.java b/src/com/android/alarmclock/ToastMaster.java deleted file mode 100644 index c4c281521..000000000 --- a/src/com/android/alarmclock/ToastMaster.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.alarmclock; - -import android.widget.Toast; - -public class ToastMaster { - - private static Toast sToast = null; - - private ToastMaster() { - - } - - public static void setToast(Toast toast) { - if (sToast != null) - sToast.cancel(); - sToast = toast; - } - - public static void cancelToast() { - if (sToast != null) - sToast.cancel(); - sToast = null; - } - -} diff --git a/src/com/android/deskclock/Alarm.java b/src/com/android/deskclock/Alarm.java new file mode 100644 index 000000000..13e4bd7b9 --- /dev/null +++ b/src/com/android/deskclock/Alarm.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2009 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.deskclock; + +import android.content.Context; +import android.database.Cursor; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.BaseColumns; +import android.text.format.DateFormat; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +public final class Alarm implements Parcelable { + + ////////////////////////////// + // Parcelable apis + ////////////////////////////// + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public Alarm createFromParcel(Parcel p) { + return new Alarm(p); + } + + public Alarm[] newArray(int size) { + return new Alarm[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel p, int flags) { + p.writeInt(id); + p.writeInt(enabled ? 1 : 0); + p.writeInt(hour); + p.writeInt(minutes); + p.writeInt(daysOfWeek.getCoded()); + p.writeLong(time); + p.writeInt(vibrate ? 1 : 0); + p.writeString(label); + p.writeParcelable(alert, flags); + p.writeInt(silent ? 1 : 0); + } + ////////////////////////////// + // end Parcelable apis + ////////////////////////////// + + ////////////////////////////// + // Column definitions + ////////////////////////////// + public static class Columns implements BaseColumns { + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://com.android.deskclock/alarm"); + + /** + * Hour in 24-hour localtime 0 - 23. + *

Type: INTEGER

+ */ + public static final String HOUR = "hour"; + + /** + * Minutes in localtime 0 - 59 + *

Type: INTEGER

+ */ + public static final String MINUTES = "minutes"; + + /** + * Days of week coded as integer + *

Type: INTEGER

+ */ + public static final String DAYS_OF_WEEK = "daysofweek"; + + /** + * Alarm time in UTC milliseconds from the epoch. + *

Type: INTEGER

+ */ + public static final String ALARM_TIME = "alarmtime"; + + /** + * True if alarm is active + *

Type: BOOLEAN

+ */ + public static final String ENABLED = "enabled"; + + /** + * True if alarm should vibrate + *

Type: BOOLEAN

+ */ + public static final String VIBRATE = "vibrate"; + + /** + * Message to show when alarm triggers + * Note: not currently used + *

Type: STRING

+ */ + public static final String MESSAGE = "message"; + + /** + * Audio alert to play when alarm triggers + *

Type: STRING

+ */ + public static final String ALERT = "alert"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = _ID + " ASC"; + + // Used when filtering enabled alarms. + public static final String WHERE_ENABLED = ENABLED + "=1"; + + static final String[] ALARM_QUERY_COLUMNS = { + _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME, + ENABLED, VIBRATE, MESSAGE, ALERT }; + + /** + * These save calls to cursor.getColumnIndexOrThrow() + * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS + */ + public static final int ALARM_ID_INDEX = 0; + public static final int ALARM_HOUR_INDEX = 1; + public static final int ALARM_MINUTES_INDEX = 2; + public static final int ALARM_DAYS_OF_WEEK_INDEX = 3; + public static final int ALARM_TIME_INDEX = 4; + public static final int ALARM_ENABLED_INDEX = 5; + public static final int ALARM_VIBRATE_INDEX = 6; + public static final int ALARM_MESSAGE_INDEX = 7; + public static final int ALARM_ALERT_INDEX = 8; + } + ////////////////////////////// + // End column definitions + ////////////////////////////// + + // Public fields + public int id; + public boolean enabled; + public int hour; + public int minutes; + public DaysOfWeek daysOfWeek; + public long time; + public boolean vibrate; + public String label; + public Uri alert; + public boolean silent; + + public Alarm(Cursor c) { + id = c.getInt(Columns.ALARM_ID_INDEX); + enabled = c.getInt(Columns.ALARM_ENABLED_INDEX) == 1; + hour = c.getInt(Columns.ALARM_HOUR_INDEX); + minutes = c.getInt(Columns.ALARM_MINUTES_INDEX); + daysOfWeek = new DaysOfWeek(c.getInt(Columns.ALARM_DAYS_OF_WEEK_INDEX)); + time = c.getLong(Columns.ALARM_TIME_INDEX); + vibrate = c.getInt(Columns.ALARM_VIBRATE_INDEX) == 1; + label = c.getString(Columns.ALARM_MESSAGE_INDEX); + String alertString = c.getString(Columns.ALARM_ALERT_INDEX); + if (Alarms.ALARM_ALERT_SILENT.equals(alertString)) { + if (Log.LOGV) { + Log.v("Alarm is marked as silent"); + } + silent = true; + } else { + if (alertString != null && alertString.length() != 0) { + alert = Uri.parse(alertString); + } + + // If the database alert is null or it failed to parse, use the + // default alert. + if (alert == null) { + alert = RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_ALARM); + } + } + } + + public Alarm(Parcel p) { + id = p.readInt(); + enabled = p.readInt() == 1; + hour = p.readInt(); + minutes = p.readInt(); + daysOfWeek = new DaysOfWeek(p.readInt()); + time = p.readLong(); + vibrate = p.readInt() == 1; + label = p.readString(); + alert = (Uri) p.readParcelable(null); + silent = p.readInt() == 1; + } + + public String getLabelOrDefault(Context context) { + if (label == null || label.length() == 0) { + return context.getString(R.string.default_label); + } + return label; + } + + /* + * Days of week code as a single int. + * 0x00: no day + * 0x01: Monday + * 0x02: Tuesday + * 0x04: Wednesday + * 0x08: Thursday + * 0x10: Friday + * 0x20: Saturday + * 0x40: Sunday + */ + static final class DaysOfWeek { + + private static int[] DAY_MAP = new int[] { + Calendar.MONDAY, + Calendar.TUESDAY, + Calendar.WEDNESDAY, + Calendar.THURSDAY, + Calendar.FRIDAY, + Calendar.SATURDAY, + Calendar.SUNDAY, + }; + + // Bitmask of all repeating days + private int mDays; + + DaysOfWeek(int days) { + mDays = days; + } + + public String toString(Context context, boolean showNever) { + StringBuilder ret = new StringBuilder(); + + // no days + if (mDays == 0) { + return showNever ? + context.getText(R.string.never).toString() : ""; + } + + // every day + if (mDays == 0x7f) { + return context.getText(R.string.every_day).toString(); + } + + // count selected days + int dayCount = 0, days = mDays; + while (days > 0) { + if ((days & 1) == 1) dayCount++; + days >>= 1; + } + + // short or long form? + DateFormatSymbols dfs = new DateFormatSymbols(); + String[] dayList = (dayCount > 1) ? + dfs.getShortWeekdays() : + dfs.getWeekdays(); + + // selected days + for (int i = 0; i < 7; i++) { + if ((mDays & (1 << i)) != 0) { + ret.append(dayList[DAY_MAP[i]]); + dayCount -= 1; + if (dayCount > 0) ret.append( + context.getText(R.string.day_concat)); + } + } + return ret.toString(); + } + + private boolean isSet(int day) { + return ((mDays & (1 << day)) > 0); + } + + public void set(int day, boolean set) { + if (set) { + mDays |= (1 << day); + } else { + mDays &= ~(1 << day); + } + } + + public void set(DaysOfWeek dow) { + mDays = dow.mDays; + } + + public int getCoded() { + return mDays; + } + + // Returns days of week encoded in an array of booleans. + public boolean[] getBooleanArray() { + boolean[] ret = new boolean[7]; + for (int i = 0; i < 7; i++) { + ret[i] = isSet(i); + } + return ret; + } + + public boolean isRepeatSet() { + return mDays != 0; + } + + /** + * returns number of days from today until next alarm + * @param c must be set to today + */ + public int getNextAlarm(Calendar c) { + if (mDays == 0) { + return -1; + } + + int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7; + + int day = 0; + int dayCount = 0; + for (; dayCount < 7; dayCount++) { + day = (today + dayCount) % 7; + if (isSet(day)) { + break; + } + } + return dayCount; + } + } +} diff --git a/src/com/android/deskclock/AlarmAlert.java b/src/com/android/deskclock/AlarmAlert.java new file mode 100644 index 000000000..732bee834 --- /dev/null +++ b/src/com/android/deskclock/AlarmAlert.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.deskclock; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.LayoutInflater; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.Toast; +import android.widget.TextView; + +import java.util.Calendar; + +/** + * Alarm Clock alarm alert: pops visible indicator and plays alarm + * tone + */ +public class AlarmAlert extends Activity { + + // These defaults must match the values in res/xml/settings.xml + private static final String DEFAULT_SNOOZE = "10"; + private static final String DEFAULT_VOLUME_BEHAVIOR = "2"; + + private Alarm mAlarm; + private int mVolumeBehavior; + + // Receives the ALARM_KILLED action from the AlarmKlaxon. + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Alarm alarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); + if (mAlarm.id == alarm.id) { + dismiss(true); + } + } + }; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mAlarm = getIntent().getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); + + // Get the volume/camera button behavior setting + final String vol = + PreferenceManager.getDefaultSharedPreferences(this) + .getString(SettingsActivity.KEY_VOLUME_BEHAVIOR, + DEFAULT_VOLUME_BEHAVIOR); + mVolumeBehavior = Integer.parseInt(vol); + + requestWindowFeature(android.view.Window.FEATURE_NO_TITLE); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + updateLayout(); + + // Register to get the alarm killed intent. + registerReceiver(mReceiver, new IntentFilter(Alarms.ALARM_KILLED)); + } + + private void setTitle() { + String label = mAlarm.getLabelOrDefault(this); + TextView title = (TextView) findViewById(R.id.alertTitle); + title.setText(label); + } + + // This method is overwritten in AlarmAlertFullScreen in order to show a + // full activity with the wallpaper as the background. + protected View inflateView(LayoutInflater inflater) { + return inflater.inflate(R.layout.alarm_alert, null); + } + + private void updateLayout() { + LayoutInflater inflater = LayoutInflater.from(this); + + setContentView(inflateView(inflater)); + + /* set clock face */ + SharedPreferences settings = + getSharedPreferences(AlarmClock.PREFERENCES, 0); + int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); + if (face < 0 || face >= AlarmClock.CLOCKS.length) { + face = 0; + } + ViewGroup clockView = (ViewGroup) findViewById(R.id.clockView); + inflater.inflate(AlarmClock.CLOCKS[face], clockView); + View clockLayout = findViewById(R.id.clock); + if (clockLayout instanceof DigitalClock) { + ((DigitalClock) clockLayout).setAnimate(); + } + + /* snooze behavior: pop a snooze confirmation view, kick alarm + manager. */ + Button snooze = (Button) findViewById(R.id.snooze); + snooze.requestFocus(); + snooze.setOnClickListener(new Button.OnClickListener() { + public void onClick(View v) { + snooze(); + } + }); + + /* dismiss button: close notification */ + findViewById(R.id.dismiss).setOnClickListener( + new Button.OnClickListener() { + public void onClick(View v) { + dismiss(false); + } + }); + + /* Set the title from the passed in alarm */ + setTitle(); + } + + // Attempt to snooze this alert. + private void snooze() { + final String snooze = + PreferenceManager.getDefaultSharedPreferences(this) + .getString(SettingsActivity.KEY_ALARM_SNOOZE, DEFAULT_SNOOZE); + int snoozeMinutes = Integer.parseInt(snooze); + + final long snoozeTime = System.currentTimeMillis() + + (1000 * 60 * snoozeMinutes); + Alarms.saveSnoozeAlert(AlarmAlert.this, mAlarm.id, snoozeTime); + + // Get the display time for the snooze and update the notification. + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(snoozeTime); + + // Append (snoozed) to the label. + String label = mAlarm.getLabelOrDefault(this); + label = getString(R.string.alarm_notify_snooze_label, label); + + // Notify the user that the alarm has been snoozed. + Intent cancelSnooze = new Intent(this, AlarmReceiver.class); + cancelSnooze.setAction(Alarms.CANCEL_SNOOZE); + cancelSnooze.putExtra(Alarms.ALARM_ID, mAlarm.id); + PendingIntent broadcast = + PendingIntent.getBroadcast(this, mAlarm.id, cancelSnooze, 0); + NotificationManager nm = getNotificationManager(); + Notification n = new Notification(R.drawable.stat_notify_alarm, + label, 0); + n.setLatestEventInfo(this, label, + getString(R.string.alarm_notify_snooze_text, + Alarms.formatTime(this, c)), broadcast); + n.deleteIntent = broadcast; + n.flags |= Notification.FLAG_AUTO_CANCEL; + nm.notify(mAlarm.id, n); + + String displayTime = getString(R.string.alarm_alert_snooze_set, + snoozeMinutes); + // Intentionally log the snooze time for debugging. + Log.v(displayTime); + + // Display the snooze minutes in a toast. + Toast.makeText(AlarmAlert.this, displayTime, Toast.LENGTH_LONG).show(); + stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); + finish(); + } + + private NotificationManager getNotificationManager() { + return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + } + + // Dismiss the alarm. + private void dismiss(boolean killed) { + // The service told us that the alarm has been killed, do not modify + // the notification or stop the service. + if (!killed) { + // Cancel the notification and stop playing the alarm + NotificationManager nm = getNotificationManager(); + nm.cancel(mAlarm.id); + stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); + } + finish(); + } + + /** + * this is called when a second alarm is triggered while a + * previous alert window is still active. + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + if (Log.LOGV) Log.v("AlarmAlert.OnNewIntent()"); + + mAlarm = intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA); + + setTitle(); + } + + @Override + protected void onStop() { + super.onStop(); + // Don't hang around. + finish(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (Log.LOGV) Log.v("AlarmAlert.onDestroy()"); + // No longer care about the alarm being killed. + unregisterReceiver(mReceiver); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Do this on key down to handle a few of the system keys. + boolean up = event.getAction() == KeyEvent.ACTION_UP; + switch (event.getKeyCode()) { + // Volume keys and camera keys dismiss the alarm + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_CAMERA: + case KeyEvent.KEYCODE_FOCUS: + if (up) { + switch (mVolumeBehavior) { + case 1: + snooze(); + break; + + case 2: + dismiss(false); + break; + + default: + break; + } + } + return true; + default: + break; + } + return super.dispatchKeyEvent(event); + } +} diff --git a/src/com/android/deskclock/AlarmAlertFullScreen.java b/src/com/android/deskclock/AlarmAlertFullScreen.java new file mode 100644 index 000000000..fe7d61d37 --- /dev/null +++ b/src/com/android/deskclock/AlarmAlertFullScreen.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009 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.deskclock; + +import android.os.Bundle; +import android.view.WindowManager; + +/** + * Full screen alarm alert: pops visible indicator and plays alarm tone. This + * activity displays the alert in full screen in order to be secure. The + * background is the current wallpaper. + */ +public class AlarmAlertFullScreen extends AlarmAlert { + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + } + + @Override + public void onBackPressed() { + // Don't allow back to dismiss. + return; + } +} diff --git a/src/com/android/deskclock/AlarmAlertWakeLock.java b/src/com/android/deskclock/AlarmAlertWakeLock.java new file mode 100644 index 000000000..92b79e317 --- /dev/null +++ b/src/com/android/deskclock/AlarmAlertWakeLock.java @@ -0,0 +1,53 @@ +/* + * 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.deskclock; + +import android.content.Context; +import android.os.PowerManager; + +/** + * Hold a wakelock that can be acquired in the AlarmReceiver and + * released in the AlarmAlert activity + */ +class AlarmAlertWakeLock { + + private static PowerManager.WakeLock sCpuWakeLock; + + static void acquireCpuWakeLock(Context context) { + Log.v("Acquiring cpu wake lock"); + if (sCpuWakeLock != null) { + return; + } + + PowerManager pm = + (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + sCpuWakeLock = pm.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK | + PowerManager.ACQUIRE_CAUSES_WAKEUP | + PowerManager.ON_AFTER_RELEASE, Log.LOGTAG); + sCpuWakeLock.acquire(); + } + + static void releaseCpuLock() { + Log.v("Releasing cpu wake lock"); + if (sCpuWakeLock != null) { + sCpuWakeLock.release(); + sCpuWakeLock = null; + } + } +} diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java new file mode 100644 index 000000000..cb9fc35d2 --- /dev/null +++ b/src/com/android/deskclock/AlarmClock.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.deskclock; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.CursorAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.CheckBox; + +import java.util.Calendar; +import java.text.DateFormatSymbols; + +/** + * AlarmClock application. + */ +public class AlarmClock extends Activity implements OnItemClickListener { + + final static String PREFERENCES = "AlarmClock"; + final static String PREF_CLOCK_FACE = "face"; + final static String PREF_SHOW_CLOCK = "show_clock"; + + /** Cap alarm count at this number */ + final static int MAX_ALARM_COUNT = 12; + + /** This must be false for production. If true, turns on logging, + test code, etc. */ + final static boolean DEBUG = false; + + private SharedPreferences mPrefs; + private LayoutInflater mFactory; + private ViewGroup mClockLayout; + private View mClock = null; + private ListView mAlarmsList; + private Cursor mCursor; + + private String mAm, mPm; + + /** + * Which clock face to show + */ + private int mFace = -1; + + /* + * FIXME: it would be nice for this to live in an xml config file. + */ + final static int[] CLOCKS = { + R.layout.clock_basic_bw, + R.layout.clock_googly, + R.layout.clock_droid2, + R.layout.clock_droids, + R.layout.digital_clock + }; + + private class AlarmTimeAdapter extends CursorAdapter { + public AlarmTimeAdapter(Context context, Cursor cursor) { + super(context, cursor); + } + + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View ret = mFactory.inflate(R.layout.alarm_time, parent, false); + + ((TextView) ret.findViewById(R.id.am)).setText(mAm); + ((TextView) ret.findViewById(R.id.pm)).setText(mPm); + + DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); + digitalClock.setLive(false); + if (Log.LOGV) Log.v("newView " + cursor.getPosition()); + return ret; + } + + public void bindView(View view, Context context, Cursor cursor) { + final Alarm alarm = new Alarm(cursor); + + CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton); + onButton.setChecked(alarm.enabled); + onButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + boolean isChecked = ((CheckBox) v).isChecked(); + Alarms.enableAlarm(AlarmClock.this, alarm.id, + isChecked); + if (isChecked) { + SetAlarm.popAlarmSetToast(AlarmClock.this, + alarm.hour, alarm.minutes, alarm.daysOfWeek); + } + } + }); + + DigitalClock digitalClock = + (DigitalClock) view.findViewById(R.id.digitalClock); + + // set the alarm text + final Calendar c = Calendar.getInstance(); + c.set(Calendar.HOUR_OF_DAY, alarm.hour); + c.set(Calendar.MINUTE, alarm.minutes); + digitalClock.updateTime(c); + + // Set the repeat text or leave it blank if it does not repeat. + TextView daysOfWeekView = + (TextView) digitalClock.findViewById(R.id.daysOfWeek); + final String daysOfWeekStr = + alarm.daysOfWeek.toString(AlarmClock.this, false); + if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) { + daysOfWeekView.setText(daysOfWeekStr); + daysOfWeekView.setVisibility(View.VISIBLE); + } else { + daysOfWeekView.setVisibility(View.GONE); + } + + // Display the label + TextView labelView = + (TextView) digitalClock.findViewById(R.id.label); + if (alarm.label != null && alarm.label.length() != 0) { + labelView.setText(alarm.label); + labelView.setVisibility(View.VISIBLE); + } else { + labelView.setVisibility(View.GONE); + } + } + }; + + @Override + public boolean onContextItemSelected(final MenuItem item) { + final AdapterContextMenuInfo info = + (AdapterContextMenuInfo) item.getMenuInfo(); + final int id = (int) info.id; + switch (item.getItemId()) { + case R.id.delete_alarm: + // Confirm that the alarm will be deleted. + new AlertDialog.Builder(this) + .setTitle(getString(R.string.delete_alarm)) + .setMessage(getString(R.string.delete_alarm_confirm)) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface d, + int w) { + Alarms.deleteAlarm(AlarmClock.this, id); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + return true; + + case R.id.enable_alarm: + final Cursor c = (Cursor) mAlarmsList.getAdapter() + .getItem(info.position); + final Alarm alarm = new Alarm(c); + Alarms.enableAlarm(this, alarm.id, !alarm.enabled); + if (!alarm.enabled) { + SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes, + alarm.daysOfWeek); + } + return true; + + default: + break; + } + return super.onContextItemSelected(item); + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + String[] ampm = new DateFormatSymbols().getAmPmStrings(); + mAm = ampm[0]; + mPm = ampm[1]; + + mFactory = LayoutInflater.from(this); + mPrefs = getSharedPreferences(PREFERENCES, 0); + mCursor = Alarms.getAlarmsCursor(getContentResolver()); + + updateLayout(); + setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateLayout(); + inflateClock(); + } + + private void updateLayout() { + setContentView(R.layout.alarm_clock); + mAlarmsList = (ListView) findViewById(R.id.alarms_list); + mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); + mAlarmsList.setVerticalScrollBarEnabled(true); + mAlarmsList.setOnItemClickListener(this); + mAlarmsList.setOnCreateContextMenuListener(this); + + mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); + mClockLayout.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + final Intent intent = + new Intent(AlarmClock.this, ClockPicker.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + + int face = mPrefs.getInt(PREF_CLOCK_FACE, 0); + if (mFace != face) { + if (face < 0 || face >= AlarmClock.CLOCKS.length) { + mFace = 0; + } else { + mFace = face; + } + inflateClock(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + ToastMaster.cancelToast(); + mCursor.deactivate(); + } + + protected void inflateClock() { + if (mClock != null) { + mClockLayout.removeView(mClock); + } + + LayoutInflater.from(this).inflate(CLOCKS[mFace], mClockLayout); + mClock = findViewById(R.id.clock); + + TextView am = (TextView) findViewById(R.id.am); + TextView pm = (TextView) findViewById(R.id.pm); + + if (am != null) { + am.setText(mAm); + } + if (pm != null) { + pm.setText(mPm); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate our menu. + getMenuInflater().inflate(R.menu.main_menu, menu); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, + ContextMenuInfo menuInfo) { + // Inflate the menu from xml. + getMenuInflater().inflate(R.menu.context_menu, menu); + + // Use the current item to create a custom view for the header. + final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + final Cursor c = + (Cursor) mAlarmsList.getAdapter().getItem((int) info.position); + final Alarm alarm = new Alarm(c); + + // Construct the Calendar to compute the time. + final Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, alarm.hour); + cal.set(Calendar.MINUTE, alarm.minutes); + final String time = Alarms.formatTime(this, cal); + + // Inflate the custom view and set each TextView's text. + final View v = mFactory.inflate(R.layout.context_menu_header, null); + TextView textView = (TextView) v.findViewById(R.id.header_time); + textView.setText(time); + textView = (TextView) v.findViewById(R.id.header_label); + textView.setText(alarm.label); + + // Set the custom view on the menu. + menu.setHeaderView(v); + // Change the text to "disable" if the alarm is already enabled. + if (alarm.enabled) { + menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm); + } + } + + public void onItemClick(AdapterView parent, View v, int pos, long id) { + Intent intent = new Intent(this, SetAlarm.class); + intent.putExtra(Alarms.ALARM_ID, (int) id); + startActivity(intent); + } + + /** + * Only allow user to add a new alarm if there are fewer than + * MAX_ALARM_COUNT + */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.menu_add_alarm).setVisible( + mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); + menu.findItem(R.id.menu_toggle_clock).setTitle( + getClockVisibility() ? R.string.hide_clock + : R.string.show_clock); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_add_alarm: + Uri uri = Alarms.addAlarm(getContentResolver()); + // FIXME: scroll to new item? + String segment = uri.getPathSegments().get(1); + int newId = Integer.parseInt(segment); + if (Log.LOGV) { + Log.v("In AlarmClock, new alarm id = " + newId); + } + Intent intent = new Intent(this, SetAlarm.class); + intent.putExtra(Alarms.ALARM_ID, newId); + startActivity(intent); + return true; + + case R.id.menu_toggle_clock: + setClockVisibility(!getClockVisibility()); + saveClockVisibility(); + return true; + + case R.id.menu_settings: + startActivity(new Intent(this, SettingsActivity.class)); + return true; + } + + return super.onOptionsItemSelected(item); + } + + + private boolean getClockVisibility() { + return mClockLayout.getVisibility() == View.VISIBLE; + } + + private void setClockVisibility(boolean visible) { + mClockLayout.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + private void saveClockVisibility() { + mPrefs.edit().putBoolean(PREF_SHOW_CLOCK, getClockVisibility()).commit(); + } +} diff --git a/src/com/android/deskclock/AlarmInitReceiver.java b/src/com/android/deskclock/AlarmInitReceiver.java new file mode 100644 index 000000000..3ab8c62ba --- /dev/null +++ b/src/com/android/deskclock/AlarmInitReceiver.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.deskclock; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; + +public class AlarmInitReceiver extends BroadcastReceiver { + + /** + * Sets alarm on ACTION_BOOT_COMPLETED. Resets alarm on + * TIME_SET, TIMEZONE_CHANGED + */ + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Log.LOGV) Log.v("AlarmInitReceiver" + action); + + if (context.getContentResolver() == null) { + Log.e("AlarmInitReceiver: FAILURE unable to get content resolver. Alarms inactive."); + return; + } + if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { + Alarms.saveSnoozeAlert(context, -1, -1); + Alarms.disableExpiredAlarms(context); + } + Alarms.setNextAlert(context); + } +} diff --git a/src/com/android/deskclock/AlarmKlaxon.java b/src/com/android/deskclock/AlarmKlaxon.java new file mode 100644 index 000000000..b8f4b6b25 --- /dev/null +++ b/src/com/android/deskclock/AlarmKlaxon.java @@ -0,0 +1,287 @@ +/* + * 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.deskclock; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetFileDescriptor; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnErrorListener; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Vibrator; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; + +/** + * Manages alarms and vibe. Runs as a service so that it can continue to play + * if another activity overrides the AlarmAlert dialog. + */ +public class AlarmKlaxon extends Service { + + /** Play alarm up to 10 minutes before silencing */ + private static final int ALARM_TIMEOUT_SECONDS = 10 * 60; + + private static final long[] sVibratePattern = new long[] { 500, 500 }; + + private boolean mPlaying = false; + private Vibrator mVibrator; + private MediaPlayer mMediaPlayer; + private Alarm mCurrentAlarm; + private long mStartTime; + private TelephonyManager mTelephonyManager; + private int mInitialCallState; + + // Internal messages + private static final int KILLER = 1000; + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case KILLER: + if (Log.LOGV) { + Log.v("*********** Alarm killer triggered ***********"); + } + sendKillBroadcast((Alarm) msg.obj); + stopSelf(); + break; + } + } + }; + + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String ignored) { + // The user might already be in a call when the alarm fires. When + // we register onCallStateChanged, we get the initial in-call state + // which kills the alarm. Check against the initial call state so + // we don't kill the alarm during a call. + if (state != TelephonyManager.CALL_STATE_IDLE + && state != mInitialCallState) { + sendKillBroadcast(mCurrentAlarm); + stopSelf(); + } + } + }; + + @Override + public void onCreate() { + mVibrator = new Vibrator(); + // Listen for incoming calls to kill the alarm. + mTelephonyManager = + (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); + mTelephonyManager.listen( + mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + AlarmAlertWakeLock.acquireCpuWakeLock(this); + } + + @Override + public void onDestroy() { + stop(); + // Stop listening for incoming calls. + mTelephonyManager.listen(mPhoneStateListener, 0); + AlarmAlertWakeLock.releaseCpuLock(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // No intent, tell the system not to restart us. + if (intent == null) { + stopSelf(); + return START_NOT_STICKY; + } + + final Alarm alarm = intent.getParcelableExtra( + Alarms.ALARM_INTENT_EXTRA); + + if (alarm == null) { + Log.v("AlarmKlaxon failed to parse the alarm from the intent"); + stopSelf(); + return START_NOT_STICKY; + } + + if (mCurrentAlarm != null) { + sendKillBroadcast(mCurrentAlarm); + } + + play(alarm); + mCurrentAlarm = alarm; + // Record the initial call state here so that the new alarm has the + // newest state. + mInitialCallState = mTelephonyManager.getCallState(); + + return START_STICKY; + } + + private void sendKillBroadcast(Alarm alarm) { + long millis = System.currentTimeMillis() - mStartTime; + int minutes = (int) Math.round(millis / 60000.0); + Intent alarmKilled = new Intent(Alarms.ALARM_KILLED); + alarmKilled.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); + alarmKilled.putExtra(Alarms.ALARM_KILLED_TIMEOUT, minutes); + sendBroadcast(alarmKilled); + } + + // Volume suggested by media team for in-call alarms. + private static final float IN_CALL_VOLUME = 0.125f; + + private void play(Alarm alarm) { + // stop() checks to see if we are already playing. + stop(); + + if (Log.LOGV) { + Log.v("AlarmKlaxon.play() " + alarm.id + " alert " + alarm.alert); + } + + if (!alarm.silent) { + Uri alert = alarm.alert; + // Fall back on the default alarm if the database does not have an + // alarm stored. + if (alert == null) { + alert = RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_ALARM); + if (Log.LOGV) { + Log.v("Using default alarm: " + alert.toString()); + } + } + + // TODO: Reuse mMediaPlayer instead of creating a new one and/or use + // RingtoneManager. + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnErrorListener(new OnErrorListener() { + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e("Error occurred while playing audio."); + mp.stop(); + mp.release(); + mMediaPlayer = null; + return true; + } + }); + + try { + // Check if we are in a call. If we are, use the in-call alarm + // resource at a low volume to not disrupt the call. + if (mTelephonyManager.getCallState() + != TelephonyManager.CALL_STATE_IDLE) { + Log.v("Using the in-call alarm"); + mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); + setDataSourceFromResource(getResources(), mMediaPlayer, + R.raw.in_call_alarm); + } else { + mMediaPlayer.setDataSource(this, alert); + } + startAlarm(mMediaPlayer); + } catch (Exception ex) { + Log.v("Using the fallback ringtone"); + // The alert may be on the sd card which could be busy right + // now. Use the fallback ringtone. + try { + // Must reset the media player to clear the error state. + mMediaPlayer.reset(); + setDataSourceFromResource(getResources(), mMediaPlayer, + com.android.internal.R.raw.fallbackring); + startAlarm(mMediaPlayer); + } catch (Exception ex2) { + // At this point we just don't play anything. + Log.e("Failed to play fallback ringtone", ex2); + } + } + } + + /* Start the vibrator after everything is ok with the media player */ + if (alarm.vibrate) { + mVibrator.vibrate(sVibratePattern, 0); + } else { + mVibrator.cancel(); + } + + enableKiller(alarm); + mPlaying = true; + mStartTime = System.currentTimeMillis(); + } + + // Do the common stuff when starting the alarm. + private void startAlarm(MediaPlayer player) + throws java.io.IOException, IllegalArgumentException, + IllegalStateException { + player.setAudioStreamType(AudioManager.STREAM_ALARM); + player.setLooping(true); + player.prepare(); + player.start(); + } + + private void setDataSourceFromResource(Resources resources, + MediaPlayer player, int res) throws java.io.IOException { + AssetFileDescriptor afd = resources.openRawResourceFd(res); + if (afd != null) { + player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), + afd.getLength()); + afd.close(); + } + } + + /** + * Stops alarm audio and disables alarm if it not snoozed and not + * repeating + */ + public void stop() { + if (Log.LOGV) Log.v("AlarmKlaxon.stop()"); + if (mPlaying) { + mPlaying = false; + + // Stop audio playing + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + + // Stop vibrator + mVibrator.cancel(); + } + disableKiller(); + } + + /** + * Kills alarm audio after ALARM_TIMEOUT_SECONDS, so the alarm + * won't run all day. + * + * This just cancels the audio, but leaves the notification + * popped, so the user will know that the alarm tripped. + */ + private void enableKiller(Alarm alarm) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(KILLER, alarm), + 1000 * ALARM_TIMEOUT_SECONDS); + } + + private void disableKiller() { + mHandler.removeMessages(KILLER); + } + + +} diff --git a/src/com/android/deskclock/AlarmPreference.java b/src/com/android/deskclock/AlarmPreference.java new file mode 100644 index 000000000..c1b94f85d --- /dev/null +++ b/src/com/android/deskclock/AlarmPreference.java @@ -0,0 +1,65 @@ +/* + * 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.deskclock; + +import android.content.Context; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.preference.RingtonePreference; +import android.util.AttributeSet; + +/** + * The RingtonePreference does not have a way to get/set the current ringtone so + * we override onSaveRingtone and onRestoreRingtone to get the same behavior. + */ +public class AlarmPreference extends RingtonePreference { + private Uri mAlert; + + public AlarmPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onSaveRingtone(Uri ringtoneUri) { + setAlert(ringtoneUri); + } + + @Override + protected Uri onRestoreRingtone() { + return mAlert; + } + + public void setAlert(Uri alert) { + mAlert = alert; + if (alert != null) { + final Ringtone r = RingtoneManager.getRingtone(getContext(), alert); + if (r != null) { + setSummary(r.getTitle(getContext())); + } + } else { + setSummary(R.string.silent_alarm_summary); + } + } + + public String getAlertString() { + if (mAlert != null) { + return mAlert.toString(); + } + return Alarms.ALARM_ALERT_SILENT; + } +} diff --git a/src/com/android/deskclock/AlarmProvider.java b/src/com/android/deskclock/AlarmProvider.java new file mode 100644 index 000000000..c635b0113 --- /dev/null +++ b/src/com/android/deskclock/AlarmProvider.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.deskclock; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; + +public class AlarmProvider extends ContentProvider { + private SQLiteOpenHelper mOpenHelper; + + private static final int ALARMS = 1; + private static final int ALARMS_ID = 2; + private static final UriMatcher sURLMatcher = new UriMatcher( + UriMatcher.NO_MATCH); + + static { + sURLMatcher.addURI("com.android.deskclock", "alarm", ALARMS); + sURLMatcher.addURI("com.android.deskclock", "alarm/#", ALARMS_ID); + } + + private static class DatabaseHelper extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "alarms.db"; + private static final int DATABASE_VERSION = 5; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE alarms (" + + "_id INTEGER PRIMARY KEY," + + "hour INTEGER, " + + "minutes INTEGER, " + + "daysofweek INTEGER, " + + "alarmtime INTEGER, " + + "enabled INTEGER, " + + "vibrate INTEGER, " + + "message TEXT, " + + "alert TEXT);"); + + // insert default alarms + String insertMe = "INSERT INTO alarms " + + "(hour, minutes, daysofweek, alarmtime, enabled, vibrate, message, alert) " + + "VALUES "; + db.execSQL(insertMe + "(7, 0, 127, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(8, 30, 31, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(9, 00, 0, 0, 0, 1, '', '');"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { + if (Log.LOGV) Log.v( + "Upgrading alarms database from version " + + oldVersion + " to " + currentVersion + + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS alarms"); + onCreate(db); + } + } + + public AlarmProvider() { + } + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + return true; + } + + @Override + public Cursor query(Uri url, String[] projectionIn, String selection, + String[] selectionArgs, String sort) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + // Generate the body of the query + int match = sURLMatcher.match(url); + switch (match) { + case ALARMS: + qb.setTables("alarms"); + break; + case ALARMS_ID: + qb.setTables("alarms"); + qb.appendWhere("_id="); + qb.appendWhere(url.getPathSegments().get(1)); + break; + default: + throw new IllegalArgumentException("Unknown URL " + url); + } + + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, + null, null, sort); + + if (ret == null) { + if (Log.LOGV) Log.v("Alarms.query: failed"); + } else { + ret.setNotificationUri(getContext().getContentResolver(), url); + } + + return ret; + } + + @Override + public String getType(Uri url) { + int match = sURLMatcher.match(url); + switch (match) { + case ALARMS: + return "vnd.android.cursor.dir/alarms"; + case ALARMS_ID: + return "vnd.android.cursor.item/alarms"; + default: + throw new IllegalArgumentException("Unknown URL"); + } + } + + @Override + public int update(Uri url, ContentValues values, String where, String[] whereArgs) { + int count; + long rowId = 0; + int match = sURLMatcher.match(url); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + switch (match) { + case ALARMS_ID: { + String segment = url.getPathSegments().get(1); + rowId = Long.parseLong(segment); + count = db.update("alarms", values, "_id=" + rowId, null); + break; + } + default: { + throw new UnsupportedOperationException( + "Cannot update URL: " + url); + } + } + if (Log.LOGV) Log.v("*** notifyChange() rowId: " + rowId + " url " + url); + getContext().getContentResolver().notifyChange(url, null); + return count; + } + + @Override + public Uri insert(Uri url, ContentValues initialValues) { + if (sURLMatcher.match(url) != ALARMS) { + throw new IllegalArgumentException("Cannot insert into URL: " + url); + } + + ContentValues values; + if (initialValues != null) + values = new ContentValues(initialValues); + else + values = new ContentValues(); + + if (!values.containsKey(Alarm.Columns.HOUR)) + values.put(Alarm.Columns.HOUR, 0); + + if (!values.containsKey(Alarm.Columns.MINUTES)) + values.put(Alarm.Columns.MINUTES, 0); + + if (!values.containsKey(Alarm.Columns.DAYS_OF_WEEK)) + values.put(Alarm.Columns.DAYS_OF_WEEK, 0); + + if (!values.containsKey(Alarm.Columns.ALARM_TIME)) + values.put(Alarm.Columns.ALARM_TIME, 0); + + if (!values.containsKey(Alarm.Columns.ENABLED)) + values.put(Alarm.Columns.ENABLED, 0); + + if (!values.containsKey(Alarm.Columns.VIBRATE)) + values.put(Alarm.Columns.VIBRATE, 1); + + if (!values.containsKey(Alarm.Columns.MESSAGE)) + values.put(Alarm.Columns.MESSAGE, ""); + + if (!values.containsKey(Alarm.Columns.ALERT)) + values.put(Alarm.Columns.ALERT, ""); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert("alarms", Alarm.Columns.MESSAGE, values); + if (rowId < 0) { + throw new SQLException("Failed to insert row into " + url); + } + if (Log.LOGV) Log.v("Added alarm rowId = " + rowId); + + Uri newUrl = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, rowId); + getContext().getContentResolver().notifyChange(newUrl, null); + return newUrl; + } + + public int delete(Uri url, String where, String[] whereArgs) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count; + long rowId = 0; + switch (sURLMatcher.match(url)) { + case ALARMS: + count = db.delete("alarms", where, whereArgs); + break; + case ALARMS_ID: + String segment = url.getPathSegments().get(1); + rowId = Long.parseLong(segment); + if (TextUtils.isEmpty(where)) { + where = "_id=" + segment; + } else { + where = "_id=" + segment + " AND (" + where + ")"; + } + count = db.delete("alarms", where, whereArgs); + break; + default: + throw new IllegalArgumentException("Cannot delete from URL: " + url); + } + + getContext().getContentResolver().notifyChange(url, null); + return count; + } +} diff --git a/src/com/android/deskclock/AlarmReceiver.java b/src/com/android/deskclock/AlarmReceiver.java new file mode 100644 index 000000000..18457b65f --- /dev/null +++ b/src/com/android/deskclock/AlarmReceiver.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.deskclock; + +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.database.Cursor; +import android.os.Parcel; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Glue class: connects AlarmAlert IntentReceiver to AlarmAlert + * activity. Passes through Alarm ID. + */ +public class AlarmReceiver extends BroadcastReceiver { + + /** If the alarm is older than STALE_WINDOW seconds, ignore. It + is probably the result of a time or timezone change */ + private final static int STALE_WINDOW = 60 * 30; + + @Override + public void onReceive(Context context, Intent intent) { + // Take care of the easy intents first. + if (Alarms.CLEAR_NOTIFICATION.equals(intent.getAction())) { + // If this is the "Clear All Notifications" intent, stop the alarm + // service and return. + context.stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); + return; + } else if (Alarms.ALARM_KILLED.equals(intent.getAction())) { + // The alarm has been killed, update the notification + updateNotification(context, (Alarm) + intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA), + intent.getIntExtra(Alarms.ALARM_KILLED_TIMEOUT, -1)); + return; + } else if (Alarms.CANCEL_SNOOZE.equals(intent.getAction())) { + Alarms.saveSnoozeAlert(context, -1, -1); + return; + } + + Alarm alarm = null; + // Grab the alarm from the intent. Since the remote AlarmManagerService + // fills in the Intent to add some extra data, it must unparcel the + // Alarm object. It throws a ClassNotFoundException when unparcelling. + // To avoid this, do the marshalling ourselves. + final byte[] data = intent.getByteArrayExtra(Alarms.ALARM_RAW_DATA); + if (data != null) { + Parcel in = Parcel.obtain(); + in.unmarshall(data, 0, data.length); + in.setDataPosition(0); + alarm = Alarm.CREATOR.createFromParcel(in); + } + + if (alarm == null) { + Log.v("AlarmReceiver failed to parse the alarm from the intent"); + return; + } + + // Intentionally verbose: always log the alarm time to provide useful + // information in bug reports. + long now = System.currentTimeMillis(); + SimpleDateFormat format = + new SimpleDateFormat("HH:mm:ss.SSS aaa"); + Log.v("AlarmReceiver.onReceive() id " + alarm.id + " setFor " + + format.format(new Date(alarm.time))); + + if (now > alarm.time + STALE_WINDOW * 1000) { + if (Log.LOGV) { + Log.v("AlarmReceiver ignoring stale alarm"); + } + return; + } + + // Maintain a cpu wake lock until the AlarmAlert and AlarmKlaxon can + // pick it up. + AlarmAlertWakeLock.acquireCpuWakeLock(context); + + /* Close dialogs and window shade */ + Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.sendBroadcast(closeDialogs); + + // Decide which activity to start based on the state of the keyguard. + Class c = AlarmAlert.class; + KeyguardManager km = (KeyguardManager) context.getSystemService( + Context.KEYGUARD_SERVICE); + if (km.inKeyguardRestrictedInputMode()) { + // Use the full screen activity for security. + c = AlarmAlertFullScreen.class; + } + + /* launch UI, explicitly stating that this is not due to user action + * so that the current app's notification management is not disturbed */ + Intent alarmAlert = new Intent(context, c); + alarmAlert.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); + alarmAlert.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_NO_USER_ACTION); + context.startActivity(alarmAlert); + + // Disable the snooze alert if this alarm is the snooze. + Alarms.disableSnoozeAlert(context, alarm.id); + // Disable this alarm if it does not repeat. + if (!alarm.daysOfWeek.isRepeatSet()) { + Alarms.enableAlarm(context, alarm.id, false); + } else { + // Enable the next alert if there is one. The above call to + // enableAlarm will call setNextAlert so avoid calling it twice. + Alarms.setNextAlert(context); + } + + // Play the alarm alert and vibrate the device. + Intent playAlarm = new Intent(Alarms.ALARM_ALERT_ACTION); + playAlarm.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); + context.startService(playAlarm); + + // Trigger a notification that, when clicked, will show the alarm alert + // dialog. No need to check for fullscreen since this will always be + // launched from a user action. + Intent notify = new Intent(context, AlarmAlert.class); + notify.putExtra(Alarms.ALARM_INTENT_EXTRA, alarm); + PendingIntent pendingNotify = PendingIntent.getActivity(context, + alarm.id, notify, 0); + + // Use the alarm's label or the default label as the ticker text and + // main text of the notification. + String label = alarm.getLabelOrDefault(context); + Notification n = new Notification(R.drawable.stat_notify_alarm, + label, alarm.time); + n.setLatestEventInfo(context, label, + context.getString(R.string.alarm_notify_text), + pendingNotify); + n.flags |= Notification.FLAG_SHOW_LIGHTS; + n.ledARGB = 0xFF00FF00; + n.ledOnMS = 500; + n.ledOffMS = 500; + + // Set the deleteIntent for when the user clicks "Clear All + // Notifications" + Intent clearAll = new Intent(context, AlarmReceiver.class); + clearAll.setAction(Alarms.CLEAR_NOTIFICATION); + n.deleteIntent = PendingIntent.getBroadcast(context, 0, clearAll, 0); + + // Send the notification using the alarm id to easily identify the + // correct notification. + NotificationManager nm = getNotificationManager(context); + nm.notify(alarm.id, n); + } + + private NotificationManager getNotificationManager(Context context) { + return (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + } + + private void updateNotification(Context context, Alarm alarm, int timeout) { + NotificationManager nm = getNotificationManager(context); + + // If the alarm is null, just cancel the notification. + if (alarm == null) { + if (Log.LOGV) { + Log.v("Cannot update notification for killer callback"); + } + return; + } + + // Launch SetAlarm when clicked. + Intent viewAlarm = new Intent(context, SetAlarm.class); + viewAlarm.putExtra(Alarms.ALARM_ID, alarm.id); + PendingIntent intent = + PendingIntent.getActivity(context, alarm.id, viewAlarm, 0); + + // Update the notification to indicate that the alert has been + // silenced. + String label = alarm.getLabelOrDefault(context); + Notification n = new Notification(R.drawable.stat_notify_alarm, + label, alarm.time); + n.setLatestEventInfo(context, label, + context.getString(R.string.alarm_alert_alert_silenced, timeout), + intent); + n.flags |= Notification.FLAG_AUTO_CANCEL; + nm.notify(alarm.id, n); + } +} diff --git a/src/com/android/deskclock/Alarms.java b/src/com/android/deskclock/Alarms.java new file mode 100644 index 000000000..cd47b1805 --- /dev/null +++ b/src/com/android/deskclock/Alarms.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.deskclock; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.os.Parcel; +import android.provider.Settings; +import android.text.format.DateFormat; + +import java.util.Calendar; +import java.text.DateFormatSymbols; + +/** + * The Alarms provider supplies info about Alarm Clock settings + */ +public class Alarms { + + // This action triggers the AlarmReceiver as well as the AlarmKlaxon. It + // is a public action used in the manifest for receiving Alarm broadcasts + // from the alarm manager. + public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT"; + + // This is a private action used when the user clears all notifications. + public static final String CLEAR_NOTIFICATION = "clear_notification"; + + // This is a private action used by the AlarmKlaxon to update the UI to + // show the alarm has been killed. + public static final String ALARM_KILLED = "alarm_killed"; + + // Extra in the ALARM_KILLED intent to indicate to the user how long the + // alarm played before being killed. + public static final String ALARM_KILLED_TIMEOUT = "alarm_killed_timeout"; + + // This string is used to indicate a silent alarm in the db. + public static final String ALARM_ALERT_SILENT = "silent"; + + // This intent is sent from the notification when the user cancels the + // snooze alert. + public static final String CANCEL_SNOOZE = "cancel_snooze"; + + // This string is used when passing an Alarm object through an intent. + public static final String ALARM_INTENT_EXTRA = "intent.extra.alarm"; + + // This extra is the raw Alarm object data. It is used in the + // AlarmManagerService to avoid a ClassNotFoundException when filling in + // the Intent extras. + public static final String ALARM_RAW_DATA = "intent.extra.alarm_raw"; + + // This string is used to identify the alarm id passed to SetAlarm from the + // list of alarms. + public static final String ALARM_ID = "alarm_id"; + + final static String PREF_SNOOZE_ID = "snooze_id"; + final static String PREF_SNOOZE_TIME = "snooze_time"; + + private final static String DM12 = "E h:mm aa"; + private final static String DM24 = "E k:mm"; + + private final static String M12 = "h:mm aa"; + // Shared with DigitalClock + final static String M24 = "kk:mm"; + + /** + * Creates a new Alarm. + */ + public static Uri addAlarm(ContentResolver contentResolver) { + ContentValues values = new ContentValues(); + values.put(Alarm.Columns.HOUR, 8); + return contentResolver.insert(Alarm.Columns.CONTENT_URI, values); + } + + /** + * Removes an existing Alarm. If this alarm is snoozing, disables + * snooze. Sets next alert. + */ + public static void deleteAlarm( + Context context, int alarmId) { + + ContentResolver contentResolver = context.getContentResolver(); + /* If alarm is snoozing, lose it */ + disableSnoozeAlert(context, alarmId); + + Uri uri = ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId); + contentResolver.delete(uri, "", null); + + setNextAlert(context); + } + + /** + * Queries all alarms + * @return cursor over all alarms + */ + public static Cursor getAlarmsCursor(ContentResolver contentResolver) { + return contentResolver.query( + Alarm.Columns.CONTENT_URI, Alarm.Columns.ALARM_QUERY_COLUMNS, + null, null, Alarm.Columns.DEFAULT_SORT_ORDER); + } + + // Private method to get a more limited set of alarms from the database. + private static Cursor getFilteredAlarmsCursor( + ContentResolver contentResolver) { + return contentResolver.query(Alarm.Columns.CONTENT_URI, + Alarm.Columns.ALARM_QUERY_COLUMNS, Alarm.Columns.WHERE_ENABLED, + null, null); + } + + /** + * Return an Alarm object representing the alarm id in the database. + * Returns null if no alarm exists. + */ + public static Alarm getAlarm(ContentResolver contentResolver, int alarmId) { + Cursor cursor = contentResolver.query( + ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, alarmId), + Alarm.Columns.ALARM_QUERY_COLUMNS, + null, null, null); + Alarm alarm = null; + if (cursor != null) { + if (cursor.moveToFirst()) { + alarm = new Alarm(cursor); + } + cursor.close(); + } + return alarm; + } + + + /** + * A convenience method to set an alarm in the Alarms + * content provider. + * + * @param id corresponds to the _id column + * @param enabled corresponds to the ENABLED column + * @param hour corresponds to the HOUR column + * @param minutes corresponds to the MINUTES column + * @param daysOfWeek corresponds to the DAYS_OF_WEEK column + * @param time corresponds to the ALARM_TIME column + * @param vibrate corresponds to the VIBRATE column + * @param message corresponds to the MESSAGE column + * @param alert corresponds to the ALERT column + */ + public static void setAlarm( + Context context, int id, boolean enabled, int hour, int minutes, + Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message, + String alert) { + + ContentValues values = new ContentValues(8); + ContentResolver resolver = context.getContentResolver(); + // Set the alarm_time value if this alarm does not repeat. This will be + // used later to disable expired alarms. + long time = 0; + if (!daysOfWeek.isRepeatSet()) { + time = calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis(); + } + + if (Log.LOGV) Log.v( + "** setAlarm * idx " + id + " hour " + hour + " minutes " + + minutes + " enabled " + enabled + " time " + time); + + values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0); + values.put(Alarm.Columns.HOUR, hour); + values.put(Alarm.Columns.MINUTES, minutes); + values.put(Alarm.Columns.ALARM_TIME, time); + values.put(Alarm.Columns.DAYS_OF_WEEK, daysOfWeek.getCoded()); + values.put(Alarm.Columns.VIBRATE, vibrate); + values.put(Alarm.Columns.MESSAGE, message); + values.put(Alarm.Columns.ALERT, alert); + resolver.update(ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, id), + values, null, null); + + setNextAlert(context); + } + + /** + * A convenience method to enable or disable an alarm. + * + * @param id corresponds to the _id column + * @param enabled corresponds to the ENABLED column + */ + + public static void enableAlarm( + final Context context, final int id, boolean enabled) { + enableAlarmInternal(context, id, enabled); + setNextAlert(context); + } + + private static void enableAlarmInternal(final Context context, + final int id, boolean enabled) { + enableAlarmInternal(context, getAlarm(context.getContentResolver(), id), + enabled); + } + + private static void enableAlarmInternal(final Context context, + final Alarm alarm, boolean enabled) { + ContentResolver resolver = context.getContentResolver(); + + ContentValues values = new ContentValues(2); + values.put(Alarm.Columns.ENABLED, enabled ? 1 : 0); + + // If we are enabling the alarm, calculate alarm time since the time + // value in Alarm may be old. + if (enabled) { + long time = 0; + if (!alarm.daysOfWeek.isRepeatSet()) { + time = calculateAlarm(alarm.hour, alarm.minutes, + alarm.daysOfWeek).getTimeInMillis(); + } + values.put(Alarm.Columns.ALARM_TIME, time); + } + + resolver.update(ContentUris.withAppendedId( + Alarm.Columns.CONTENT_URI, alarm.id), values, null, null); + } + + public static Alarm calculateNextAlert(final Context context) { + Alarm alarm = null; + long minTime = Long.MAX_VALUE; + long now = System.currentTimeMillis(); + Cursor cursor = getFilteredAlarmsCursor(context.getContentResolver()); + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + Alarm a = new Alarm(cursor); + // A time of 0 indicates this is a repeating alarm, so + // calculate the time to get the next alert. + if (a.time == 0) { + a.time = calculateAlarm(a.hour, a.minutes, a.daysOfWeek) + .getTimeInMillis(); + } else if (a.time < now) { + // Expired alarm, disable it and move along. + enableAlarmInternal(context, a, false); + continue; + } + if (a.time < minTime) { + minTime = a.time; + alarm = a; + } + } while (cursor.moveToNext()); + } + cursor.close(); + } + return alarm; + } + + /** + * Disables non-repeating alarms that have passed. Called at + * boot. + */ + public static void disableExpiredAlarms(final Context context) { + Cursor cur = getFilteredAlarmsCursor(context.getContentResolver()); + long now = System.currentTimeMillis(); + + if (cur.moveToFirst()) { + do { + Alarm alarm = new Alarm(cur); + // A time of 0 means this alarm repeats. If the time is + // non-zero, check if the time is before now. + if (alarm.time != 0 && alarm.time < now) { + if (Log.LOGV) { + Log.v("** DISABLE " + alarm.id + " now " + now +" set " + + alarm.time); + } + enableAlarmInternal(context, alarm, false); + } + } while (cur.moveToNext()); + } + cur.close(); + } + + /** + * Called at system startup, on time/timezone change, and whenever + * the user changes alarm settings. Activates snooze if set, + * otherwise loads all alarms, activates next alert. + */ + public static void setNextAlert(final Context context) { + if (!enableSnoozeAlert(context)) { + Alarm alarm = calculateNextAlert(context); + if (alarm != null) { + enableAlert(context, alarm, alarm.time); + } else { + disableAlert(context); + } + } + } + + /** + * Sets alert in AlarmManger and StatusBar. This is what will + * actually launch the alert when the alarm triggers. + * + * @param alarm Alarm. + * @param atTimeInMillis milliseconds since epoch + */ + private static void enableAlert(Context context, final Alarm alarm, + final long atTimeInMillis) { + AlarmManager am = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + + if (Log.LOGV) { + Log.v("** setAlert id " + alarm.id + " atTime " + atTimeInMillis); + } + + Intent intent = new Intent(ALARM_ALERT_ACTION); + + // XXX: This is a slight hack to avoid an exception in the remote + // AlarmManagerService process. The AlarmManager adds extra data to + // this Intent which causes it to inflate. Since the remote process + // does not know about the Alarm class, it throws a + // ClassNotFoundException. + // + // To avoid this, we marshall the data ourselves and then parcel a plain + // byte[] array. The AlarmReceiver class knows to build the Alarm + // object from the byte[] array. + Parcel out = Parcel.obtain(); + alarm.writeToParcel(out, 0); + out.setDataPosition(0); + intent.putExtra(ALARM_RAW_DATA, out.marshall()); + + PendingIntent sender = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + + am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender); + + setStatusBarIcon(context, true); + + Calendar c = Calendar.getInstance(); + c.setTime(new java.util.Date(atTimeInMillis)); + String timeString = formatDayAndTime(context, c); + saveNextAlarm(context, timeString); + } + + /** + * Disables alert in AlarmManger and StatusBar. + * + * @param id Alarm ID. + */ + static void disableAlert(Context context) { + AlarmManager am = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + PendingIntent sender = PendingIntent.getBroadcast( + context, 0, new Intent(ALARM_ALERT_ACTION), + PendingIntent.FLAG_CANCEL_CURRENT); + am.cancel(sender); + setStatusBarIcon(context, false); + saveNextAlarm(context, ""); + } + + static void saveSnoozeAlert(final Context context, final int id, + final long time) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + SharedPreferences.Editor ed = prefs.edit(); + if (id == -1) { + clearSnoozePreference(ed); + } else { + ed.putInt(PREF_SNOOZE_ID, id); + ed.putLong(PREF_SNOOZE_TIME, time); + ed.commit(); + } + // Set the next alert after updating the snooze. + setNextAlert(context); + } + + /** + * Disable the snooze alert if the given id matches the snooze id. + */ + static void disableSnoozeAlert(final Context context, final int id) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + int snoozeId = prefs.getInt(PREF_SNOOZE_ID, -1); + if (snoozeId == -1) { + // No snooze set, do nothing. + return; + } else if (snoozeId == id) { + // This is the same id so clear the shared prefs. + clearSnoozePreference(prefs.edit()); + } + } + + // Helper to remove the snooze preference. Do not use clear because that + // will erase the clock preferences. + private static void clearSnoozePreference(final SharedPreferences.Editor ed) { + ed.remove(PREF_SNOOZE_ID); + ed.remove(PREF_SNOOZE_TIME); + ed.commit(); + }; + + /** + * If there is a snooze set, enable it in AlarmManager + * @return true if snooze is set + */ + private static boolean enableSnoozeAlert(final Context context) { + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + + int id = prefs.getInt(PREF_SNOOZE_ID, -1); + if (id == -1) { + return false; + } + long time = prefs.getLong(PREF_SNOOZE_TIME, -1); + + // Get the alarm from the db. + final Alarm alarm = getAlarm(context.getContentResolver(), id); + // The time in the database is either 0 (repeating) or a specific time + // for a non-repeating alarm. Update this value so the AlarmReceiver + // has the right time to compare. + alarm.time = time; + + enableAlert(context, alarm, time); + return true; + } + + /** + * Tells the StatusBar whether the alarm is enabled or disabled + */ + private static void setStatusBarIcon(Context context, boolean enabled) { + Intent alarmChanged = new Intent(Intent.ACTION_ALARM_CHANGED); + alarmChanged.putExtra("alarmSet", enabled); + context.sendBroadcast(alarmChanged); + } + + /** + * Given an alarm in hours and minutes, return a time suitable for + * setting in AlarmManager. + * @param hour Always in 24 hour 0-23 + * @param minute 0-59 + * @param daysOfWeek 0-59 + */ + static Calendar calculateAlarm(int hour, int minute, Alarm.DaysOfWeek daysOfWeek) { + + // start with now + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(System.currentTimeMillis()); + + int nowHour = c.get(Calendar.HOUR_OF_DAY); + int nowMinute = c.get(Calendar.MINUTE); + + // if alarm is behind current time, advance one day + if (hour < nowHour || + hour == nowHour && minute <= nowMinute) { + c.add(Calendar.DAY_OF_YEAR, 1); + } + c.set(Calendar.HOUR_OF_DAY, hour); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + + int addDays = daysOfWeek.getNextAlarm(c); + /* Log.v("** TIMES * " + c.getTimeInMillis() + " hour " + hour + + " minute " + minute + " dow " + c.get(Calendar.DAY_OF_WEEK) + " from now " + + addDays); */ + if (addDays > 0) c.add(Calendar.DAY_OF_WEEK, addDays); + return c; + } + + static String formatTime(final Context context, int hour, int minute, + Alarm.DaysOfWeek daysOfWeek) { + Calendar c = calculateAlarm(hour, minute, daysOfWeek); + return formatTime(context, c); + } + + /* used by AlarmAlert */ + static String formatTime(final Context context, Calendar c) { + String format = get24HourMode(context) ? M24 : M12; + return (c == null) ? "" : (String)DateFormat.format(format, c); + } + + /** + * Shows day and time -- used for lock screen + */ + private static String formatDayAndTime(final Context context, Calendar c) { + String format = get24HourMode(context) ? DM24 : DM12; + return (c == null) ? "" : (String)DateFormat.format(format, c); + } + + /** + * Save time of the next alarm, as a formatted string, into the system + * settings so those who care can make use of it. + */ + static void saveNextAlarm(final Context context, String timeString) { + Settings.System.putString(context.getContentResolver(), + Settings.System.NEXT_ALARM_FORMATTED, + timeString); + } + + /** + * @return true if clock is set to 24-hour mode + */ + static boolean get24HourMode(final Context context) { + return android.text.format.DateFormat.is24HourFormat(context); + } +} diff --git a/src/com/android/deskclock/AnalogAppWidgetProvider.java b/src/com/android/deskclock/AnalogAppWidgetProvider.java new file mode 100644 index 000000000..d78f82fc2 --- /dev/null +++ b/src/com/android/deskclock/AnalogAppWidgetProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2009 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.deskclock; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.provider.Calendar; +import android.provider.Calendar.Attendees; +import android.provider.Calendar.Calendars; +import android.provider.Calendar.EventsColumns; +import android.provider.Calendar.Instances; +import android.provider.Calendar.Reminders; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Config; +import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; + +import java.util.Arrays; + +/** + * Simple widget to show analog clock. + */ +public class AnalogAppWidgetProvider extends BroadcastReceiver { + static final String TAG = "AnalogAppWidgetProvider"; + + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { + RemoteViews views = new RemoteViews(context.getPackageName(), + R.layout.analog_appwidget); + + int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + + AppWidgetManager gm = AppWidgetManager.getInstance(context); + gm.updateAppWidget(appWidgetIds, views); + } + } +} + diff --git a/src/com/android/deskclock/ClockPicker.java b/src/com/android/deskclock/ClockPicker.java new file mode 100644 index 000000000..077eba1ef --- /dev/null +++ b/src/com/android/deskclock/ClockPicker.java @@ -0,0 +1,119 @@ +/* + * 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.deskclock; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Gallery; + +/** + * Clock face picker for the Alarm Clock application. + */ +public class ClockPicker extends Activity implements + AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener { + + private LayoutInflater mFactory; + private Gallery mGallery; + + private SharedPreferences mPrefs; + private View mClock; + private ViewGroup mClockLayout; + private int mPosition; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + mFactory = LayoutInflater.from(this); + setContentView(R.layout.clockpicker); + + mGallery = (Gallery) findViewById(R.id.gallery); + mGallery.setAdapter(new ClockAdapter()); + mGallery.setOnItemSelectedListener(this); + mGallery.setOnItemClickListener(this); + + mPrefs = getSharedPreferences(AlarmClock.PREFERENCES, 0); + int face = mPrefs.getInt(AlarmClock.PREF_CLOCK_FACE, 0); + if (face < 0 || face >= AlarmClock.CLOCKS.length) face = 0; + + mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); + mClockLayout.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + selectClock(mPosition); + } + }); + + mGallery.setSelection(face, false); + } + + public void onItemSelected(AdapterView parent, View v, int position, long id) { + if (mClock != null) { + mClockLayout.removeView(mClock); + } + mClock = mFactory.inflate(AlarmClock.CLOCKS[position], null); + mClockLayout.addView(mClock, 0); + mPosition = position; + } + + public void onItemClick(AdapterView parent, View v, int position, long id) { + selectClock(position); + } + + private synchronized void selectClock(int position) { + SharedPreferences.Editor ed = mPrefs.edit(); + ed.putInt(AlarmClock.PREF_CLOCK_FACE, position); + ed.commit(); + + setResult(RESULT_OK); + finish(); + } + + public void onNothingSelected(AdapterView parent) { + } + + class ClockAdapter extends BaseAdapter { + + public ClockAdapter() { + } + + public int getCount() { + return AlarmClock.CLOCKS.length; + } + + public Object getItem(int position) { + return position; + } + + public long getItemId(int position) { + return position; + } + + public View getView(final int position, View convertView, ViewGroup parent) { + View clock = mFactory.inflate(AlarmClock.CLOCKS[position], null); + return clock; + } + + } +} diff --git a/src/com/android/deskclock/DigitalClock.java b/src/com/android/deskclock/DigitalClock.java new file mode 100644 index 000000000..14205e7ae --- /dev/null +++ b/src/com/android/deskclock/DigitalClock.java @@ -0,0 +1,201 @@ +/* + * 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.deskclock; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.provider.Settings; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.Calendar; + +/** + * Displays the time + */ +public class DigitalClock extends LinearLayout { + + private final static String M12 = "h:mm"; + + private Calendar mCalendar; + private String mFormat; + private TextView mTimeDisplay; + private AmPm mAmPm; + private boolean mAnimate; + private ContentObserver mFormatChangeObserver; + private boolean mLive = true; + private boolean mAttached; + + /* called by system on minute ticks */ + private final Handler mHandler = new Handler(); + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mLive && intent.getAction().equals( + Intent.ACTION_TIMEZONE_CHANGED)) { + mCalendar = Calendar.getInstance(); + } + updateTime(); + } + }; + + static class AmPm { + private int mColorOn, mColorOff; + + private LinearLayout mAmPmLayout; + private TextView mAm, mPm; + + AmPm(View parent) { + mAmPmLayout = (LinearLayout) parent.findViewById(R.id.am_pm); + mAm = (TextView)mAmPmLayout.findViewById(R.id.am); + mPm = (TextView)mAmPmLayout.findViewById(R.id.pm); + + Resources r = parent.getResources(); + mColorOn = r.getColor(R.color.ampm_on); + mColorOff = r.getColor(R.color.ampm_off); + } + + void setShowAmPm(boolean show) { + mAmPmLayout.setVisibility(show ? View.VISIBLE : View.GONE); + } + + void setIsMorning(boolean isMorning) { + mAm.setTextColor(isMorning ? mColorOn : mColorOff); + mPm.setTextColor(isMorning ? mColorOff : mColorOn); + } + } + + private class FormatChangeObserver extends ContentObserver { + public FormatChangeObserver() { + super(new Handler()); + } + @Override + public void onChange(boolean selfChange) { + setDateFormat(); + updateTime(); + } + } + + public DigitalClock(Context context) { + this(context, null); + } + + public DigitalClock(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mTimeDisplay = (TextView) findViewById(R.id.timeDisplay); + mAmPm = new AmPm(this); + mCalendar = Calendar.getInstance(); + + setDateFormat(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (Log.LOGV) Log.v("onAttachedToWindow " + this); + + if (mAttached) return; + mAttached = true; + + if (mAnimate) { + setBackgroundResource(R.drawable.animate_circle); + /* Start the animation (looped playback by default). */ + ((AnimationDrawable) getBackground()).start(); + } + + if (mLive) { + /* monitor time ticks, time changed, timezone */ + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + } + + /* monitor 12/24-hour display preference */ + mFormatChangeObserver = new FormatChangeObserver(); + mContext.getContentResolver().registerContentObserver( + Settings.System.CONTENT_URI, true, mFormatChangeObserver); + + updateTime(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (!mAttached) return; + mAttached = false; + + Drawable background = getBackground(); + if (background instanceof AnimationDrawable) { + ((AnimationDrawable) background).stop(); + } + + if (mLive) { + mContext.unregisterReceiver(mIntentReceiver); + } + mContext.getContentResolver().unregisterContentObserver( + mFormatChangeObserver); + } + + + void updateTime(Calendar c) { + mCalendar = c; + updateTime(); + } + + private void updateTime() { + if (mLive) { + mCalendar.setTimeInMillis(System.currentTimeMillis()); + } + + CharSequence newTime = DateFormat.format(mFormat, mCalendar); + mTimeDisplay.setText(newTime); + mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0); + } + + private void setDateFormat() { + mFormat = Alarms.get24HourMode(mContext) ? Alarms.M24 : M12; + mAmPm.setShowAmPm(mFormat == M12); + } + + void setAnimate() { + mAnimate = true; + } + + void setLive(boolean live) { + mLive = live; + } +} diff --git a/src/com/android/deskclock/DockEventReceiver.java b/src/com/android/deskclock/DockEventReceiver.java new file mode 100644 index 000000000..e15e2cbfd --- /dev/null +++ b/src/com/android/deskclock/DockEventReceiver.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009 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.deskclock; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; + +/** + * BroadcastReceiver which receives {@link Intent#ACTION_DOCK_EVENT} events. + * Launches the CarDockActivity if the device is placed into a car dock. + * + * TODO: This is the wrong way to launch, as this would cause contention + * between multiple activities trying to launch if others did the same. Instead + * register for a regular intent which should fire when placed into a car dock. + */ +public class DockEventReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Intent clockIntent = new Intent(Intent.ACTION_MAIN); + clockIntent.setComponent( + new ComponentName(context, AlarmClock.class)); + clockIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + String action = intent.getAction(); + if (Intent.ACTION_DOCK_EVENT.equals(action)) { + // Code to control a sticky notification for the dock. + /* + NotificationManager notificationManager = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + + int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1); + if (dockState == Intent.EXTRA_DOCK_STATE_DESK) { + Notification n = new Notification(); + n.icon = R.drawable.notification; + n.defaults = Notification.DEFAULT_LIGHTS; + n.flags = Notification.FLAG_ONGOING_EVENT; + n.tickerText = context.getString(R.string.notification_title); + n.when = 0; + n.setLatestEventInfo( + context, + context.getString(R.string.notification_title), + context.getString(R.string.notification_text), + PendingIntent.getActivity(context, 0, clockIntent, 0)); + notificationManager.notify(0, n); + } else if (dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + notificationManager.cancelAll(); + } + */ + } else if (android.provider.Telephony.Intents.SECRET_CODE_ACTION.equals(action)) { + // The user dialed *#*#DESK#*#* + context.startActivity(clockIntent); + } + } +} diff --git a/src/com/android/deskclock/Log.java b/src/com/android/deskclock/Log.java new file mode 100644 index 000000000..7e128b9a5 --- /dev/null +++ b/src/com/android/deskclock/Log.java @@ -0,0 +1,42 @@ +/* + * 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-level logging flag + */ + +package com.android.deskclock; + +import android.os.SystemClock; +import android.util.Config; + +class Log { + public final static String LOGTAG = "AlarmClock"; + + static final boolean LOGV = AlarmClock.DEBUG ? Config.LOGD : Config.LOGV; + + static void v(String logMe) { + android.util.Log.v(LOGTAG, /* SystemClock.uptimeMillis() + " " + */ logMe); + } + + static void e(String logMe) { + android.util.Log.e(LOGTAG, logMe); + } + + static void e(String logMe, Exception ex) { + android.util.Log.e(LOGTAG, logMe, ex); + } +} diff --git a/src/com/android/deskclock/RepeatPreference.java b/src/com/android/deskclock/RepeatPreference.java new file mode 100644 index 000000000..8c07c4761 --- /dev/null +++ b/src/com/android/deskclock/RepeatPreference.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.deskclock; + +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.ListPreference; +import android.util.AttributeSet; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +public class RepeatPreference extends ListPreference { + + // Initial value that can be set with the values saved in the database. + private Alarm.DaysOfWeek mDaysOfWeek = new Alarm.DaysOfWeek(0); + // New value that will be set if a positive result comes back from the + // dialog. + private Alarm.DaysOfWeek mNewDaysOfWeek = new Alarm.DaysOfWeek(0); + + public RepeatPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + String[] weekdays = new DateFormatSymbols().getWeekdays(); + String[] values = new String[] { + weekdays[Calendar.MONDAY], + weekdays[Calendar.TUESDAY], + weekdays[Calendar.WEDNESDAY], + weekdays[Calendar.THURSDAY], + weekdays[Calendar.FRIDAY], + weekdays[Calendar.SATURDAY], + weekdays[Calendar.SUNDAY], + }; + setEntries(values); + setEntryValues(values); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + mDaysOfWeek.set(mNewDaysOfWeek); + setSummary(mDaysOfWeek.toString(getContext(), true)); + } + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + CharSequence[] entries = getEntries(); + CharSequence[] entryValues = getEntryValues(); + + builder.setMultiChoiceItems( + entries, mDaysOfWeek.getBooleanArray(), + new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int which, + boolean isChecked) { + mNewDaysOfWeek.set(which, isChecked); + } + }); + } + + public void setDaysOfWeek(Alarm.DaysOfWeek dow) { + mDaysOfWeek.set(dow); + mNewDaysOfWeek.set(dow); + setSummary(dow.toString(getContext(), true)); + } + + public Alarm.DaysOfWeek getDaysOfWeek() { + return mDaysOfWeek; + } +} diff --git a/src/com/android/deskclock/SetAlarm.java b/src/com/android/deskclock/SetAlarm.java new file mode 100644 index 000000000..1acc1d790 --- /dev/null +++ b/src/com/android/deskclock/SetAlarm.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.deskclock; + +import android.app.TimePickerDialog; +import android.content.Context; +import android.content.Intent; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TimePicker; +import android.widget.Toast; + +/** + * Manages each alarm + */ +public class SetAlarm extends PreferenceActivity + implements TimePickerDialog.OnTimeSetListener { + + private EditTextPreference mLabel; + private Preference mTimePref; + private AlarmPreference mAlarmPref; + private CheckBoxPreference mVibratePref; + private RepeatPreference mRepeatPref; + private MenuItem mDeleteAlarmItem; + private MenuItem mTestAlarmItem; + + private int mId; + private boolean mEnabled; + private int mHour; + private int mMinutes; + + /** + * Set an alarm. Requires an Alarms.ALARM_ID to be passed in as an + * extra. FIXME: Pass an Alarm object like every other Activity. + */ + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.alarm_prefs); + + // Get each preference so we can retrieve the value later. + mLabel = (EditTextPreference) findPreference("label"); + mLabel.setOnPreferenceChangeListener( + new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference p, + Object newValue) { + // Set the summary based on the new label. + p.setSummary((String) newValue); + return true; + } + }); + mTimePref = findPreference("time"); + mAlarmPref = (AlarmPreference) findPreference("alarm"); + mVibratePref = (CheckBoxPreference) findPreference("vibrate"); + mRepeatPref = (RepeatPreference) findPreference("setRepeat"); + + Intent i = getIntent(); + mId = i.getIntExtra(Alarms.ALARM_ID, -1); + if (Log.LOGV) { + Log.v("In SetAlarm, alarm id = " + mId); + } + + /* load alarm details from database */ + Alarm alarm = Alarms.getAlarm(getContentResolver(), mId); + mEnabled = alarm.enabled; + mLabel.setText(alarm.label); + mLabel.setSummary(alarm.label); + mHour = alarm.hour; + mMinutes = alarm.minutes; + mRepeatPref.setDaysOfWeek(alarm.daysOfWeek); + mVibratePref.setChecked(alarm.vibrate); + // Give the alert uri to the preference. + mAlarmPref.setAlert(alarm.alert); + updateTime(); + + // We have to do this to get the save/cancel buttons to highlight on + // their own. + getListView().setItemsCanFocus(true); + + // Grab the content view so we can modify it. + FrameLayout content = (FrameLayout) getWindow().getDecorView() + .findViewById(com.android.internal.R.id.content); + + // Get the main ListView and remove it from the content view. + ListView lv = getListView(); + content.removeView(lv); + + // Create the new LinearLayout that will become the content view and + // make it vertical. + LinearLayout ll = new LinearLayout(this); + ll.setOrientation(LinearLayout.VERTICAL); + + // Have the ListView expand to fill the screen minus the save/cancel + // buttons. + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LayoutParams.FILL_PARENT, + LayoutParams.WRAP_CONTENT); + lp.weight = 1; + ll.addView(lv, lp); + + // Inflate the buttons onto the LinearLayout. + View v = LayoutInflater.from(this).inflate( + R.layout.save_cancel_alarm, ll); + + // Attach actions to each button. + Button b = (Button) v.findViewById(R.id.alarm_save); + b.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + saveAlarm(); + finish(); + } + }); + b = (Button) v.findViewById(R.id.alarm_cancel); + b.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + finish(); + } + }); + + // Replace the old content view with our new one. + setContentView(ll); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + if (preference == mTimePref) { + new TimePickerDialog(this, this, mHour, mMinutes, + DateFormat.is24HourFormat(this)).show(); + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + @Override + public void onBackPressed() { + saveAlarm(); + finish(); + } + + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + mHour = hourOfDay; + mMinutes = minute; + updateTime(); + // If the time has been changed, enable the alarm. + mEnabled = true; + } + + private void updateTime() { + if (Log.LOGV) { + Log.v("updateTime " + mId); + } + mTimePref.setSummary(Alarms.formatTime(this, mHour, mMinutes, + mRepeatPref.getDaysOfWeek())); + } + + private void saveAlarm() { + final String alert = mAlarmPref.getAlertString(); + Alarms.setAlarm(this, mId, mEnabled, mHour, mMinutes, + mRepeatPref.getDaysOfWeek(), mVibratePref.isChecked(), + mLabel.getText(), alert); + + if (mEnabled) { + popAlarmSetToast(this, mHour, mMinutes, + mRepeatPref.getDaysOfWeek()); + } + } + + /** + * Write alarm out to persistent store and pops toast if alarm + * enabled + */ + private static void saveAlarm( + Context context, int id, boolean enabled, int hour, int minute, + Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String label, + String alert, boolean popToast) { + if (Log.LOGV) Log.v("** saveAlarm " + id + " " + label + " " + enabled + + " " + hour + " " + minute + " vibe " + vibrate); + + // Fix alert string first + Alarms.setAlarm(context, id, enabled, hour, minute, daysOfWeek, vibrate, + label, alert); + + if (enabled && popToast) { + popAlarmSetToast(context, hour, minute, daysOfWeek); + } + } + + /** + * Display a toast that tells the user how long until the alarm + * goes off. This helps prevent "am/pm" mistakes. + */ + static void popAlarmSetToast(Context context, int hour, int minute, + Alarm.DaysOfWeek daysOfWeek) { + + String toastText = formatToast(context, hour, minute, daysOfWeek); + Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); + ToastMaster.setToast(toast); + toast.show(); + } + + /** + * format "Alarm set for 2 days 7 hours and 53 minutes from + * now" + */ + static String formatToast(Context context, int hour, int minute, + Alarm.DaysOfWeek daysOfWeek) { + long alarm = Alarms.calculateAlarm(hour, minute, + daysOfWeek).getTimeInMillis(); + long delta = alarm - System.currentTimeMillis();; + long hours = delta / (1000 * 60 * 60); + long minutes = delta / (1000 * 60) % 60; + long days = hours / 24; + hours = hours % 24; + + String daySeq = (days == 0) ? "" : + (days == 1) ? context.getString(R.string.day) : + context.getString(R.string.days, Long.toString(days)); + + String minSeq = (minutes == 0) ? "" : + (minutes == 1) ? context.getString(R.string.minute) : + context.getString(R.string.minutes, Long.toString(minutes)); + + String hourSeq = (hours == 0) ? "" : + (hours == 1) ? context.getString(R.string.hour) : + context.getString(R.string.hours, Long.toString(hours)); + + boolean dispDays = days > 0; + boolean dispHour = hours > 0; + boolean dispMinute = minutes > 0; + + int index = (dispDays ? 1 : 0) | + (dispHour ? 2 : 0) | + (dispMinute ? 4 : 0); + + String[] formats = context.getResources().getStringArray(R.array.alarm_set); + return String.format(formats[index], daySeq, hourSeq, minSeq); + } + + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + mDeleteAlarmItem = menu.add(0, 0, 0, R.string.delete_alarm); + mDeleteAlarmItem.setIcon(android.R.drawable.ic_menu_delete); + + if (AlarmClock.DEBUG) { + mTestAlarmItem = menu.add(0, 0, 0, "test alarm"); + } + + return true; + } + + public boolean onOptionsItemSelected(MenuItem item) { + if (item == mDeleteAlarmItem) { + Alarms.deleteAlarm(this, mId); + finish(); + return true; + } + if (AlarmClock.DEBUG) { + if (item == mTestAlarmItem) { + setTestAlarm(); + return true; + } + } + + return false; + } + + + /** + * Test code: this is disabled for production build. Sets + * this alarm to go off on the next minute + */ + void setTestAlarm() { + + // start with now + java.util.Calendar c = java.util.Calendar.getInstance(); + c.setTimeInMillis(System.currentTimeMillis()); + + int nowHour = c.get(java.util.Calendar.HOUR_OF_DAY); + int nowMinute = c.get(java.util.Calendar.MINUTE); + + int minutes = (nowMinute + 1) % 60; + int hour = nowHour + (nowMinute == 0 ? 1 : 0); + + saveAlarm(this, mId, true, hour, minutes, mRepeatPref.getDaysOfWeek(), + true, mLabel.getText(), mAlarmPref.getAlertString(), true); + } + +} diff --git a/src/com/android/deskclock/SettingsActivity.java b/src/com/android/deskclock/SettingsActivity.java new file mode 100644 index 000000000..f28d1e73b --- /dev/null +++ b/src/com/android/deskclock/SettingsActivity.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2009 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.deskclock; + +import android.media.AudioManager; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.provider.Settings; + +/** + * Settings for the Alarm Clock. + */ +public class SettingsActivity extends PreferenceActivity + implements Preference.OnPreferenceChangeListener { + + private static final int ALARM_STREAM_TYPE_BIT = + 1 << AudioManager.STREAM_ALARM; + + private static final String KEY_ALARM_IN_SILENT_MODE = + "alarm_in_silent_mode"; + static final String KEY_ALARM_SNOOZE = + "snooze_duration"; + static final String KEY_VOLUME_BEHAVIOR = + "volume_button_setting"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + } + + @Override + protected void onResume() { + super.onResume(); + refresh(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + if (KEY_ALARM_IN_SILENT_MODE.equals(preference.getKey())) { + CheckBoxPreference pref = (CheckBoxPreference) preference; + int ringerModeStreamTypes = Settings.System.getInt( + getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + if (pref.isChecked()) { + ringerModeStreamTypes &= ~ALARM_STREAM_TYPE_BIT; + } else { + ringerModeStreamTypes |= ALARM_STREAM_TYPE_BIT; + } + + Settings.System.putInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + ringerModeStreamTypes); + + return true; + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + public boolean onPreferenceChange(Preference pref, Object newValue) { + final ListPreference listPref = (ListPreference) pref; + final int idx = listPref.findIndexOfValue((String) newValue); + listPref.setSummary(listPref.getEntries()[idx]); + return true; + } + + private void refresh() { + final CheckBoxPreference alarmInSilentModePref = + (CheckBoxPreference) findPreference(KEY_ALARM_IN_SILENT_MODE); + final int silentModeStreams = + Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + alarmInSilentModePref.setChecked( + (silentModeStreams & ALARM_STREAM_TYPE_BIT) == 0); + + final ListPreference snooze = + (ListPreference) findPreference(KEY_ALARM_SNOOZE); + snooze.setSummary(snooze.getEntry()); + snooze.setOnPreferenceChangeListener(this); + } + +} diff --git a/src/com/android/deskclock/ToastMaster.java b/src/com/android/deskclock/ToastMaster.java new file mode 100644 index 000000000..4d5169612 --- /dev/null +++ b/src/com/android/deskclock/ToastMaster.java @@ -0,0 +1,41 @@ +/* + * 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.deskclock; + +import android.widget.Toast; + +public class ToastMaster { + + private static Toast sToast = null; + + private ToastMaster() { + + } + + public static void setToast(Toast toast) { + if (sToast != null) + sToast.cancel(); + sToast = toast; + } + + public static void cancelToast() { + if (sToast != null) + sToast.cancel(); + sToast = null; + } + +} -- cgit v1.2.3 From aafb2fd272f3c97f74043c11469ad301d3bd4026 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Mon, 19 Oct 2009 08:52:35 -0400 Subject: Remove the clock face from the alarm list. Remove all the clock layouts except the digital. Remove the clock picker and any settings related to change the clock face. Next step: Move the settings and add alarm menu items to buttons. --- src/com/android/deskclock/AlarmAlert.java | 14 ---- src/com/android/deskclock/AlarmClock.java | 93 ---------------------- src/com/android/deskclock/ClockPicker.java | 119 ---------------------------- src/com/android/deskclock/DigitalClock.java | 18 ----- 4 files changed, 244 deletions(-) delete mode 100644 src/com/android/deskclock/ClockPicker.java (limited to 'src') diff --git a/src/com/android/deskclock/AlarmAlert.java b/src/com/android/deskclock/AlarmAlert.java index 732bee834..8abbd7183 100644 --- a/src/com/android/deskclock/AlarmAlert.java +++ b/src/com/android/deskclock/AlarmAlert.java @@ -105,20 +105,6 @@ public class AlarmAlert extends Activity { setContentView(inflateView(inflater)); - /* set clock face */ - SharedPreferences settings = - getSharedPreferences(AlarmClock.PREFERENCES, 0); - int face = settings.getInt(AlarmClock.PREF_CLOCK_FACE, 0); - if (face < 0 || face >= AlarmClock.CLOCKS.length) { - face = 0; - } - ViewGroup clockView = (ViewGroup) findViewById(R.id.clockView); - inflater.inflate(AlarmClock.CLOCKS[face], clockView); - View clockLayout = findViewById(R.id.clock); - if (clockLayout instanceof DigitalClock) { - ((DigitalClock) clockLayout).setAnimate(); - } - /* snooze behavior: pop a snooze confirmation view, kick alarm manager. */ Button snooze = (Button) findViewById(R.id.snooze); diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java index cb9fc35d2..4b339d190 100644 --- a/src/com/android/deskclock/AlarmClock.java +++ b/src/com/android/deskclock/AlarmClock.java @@ -54,8 +54,6 @@ import java.text.DateFormatSymbols; public class AlarmClock extends Activity implements OnItemClickListener { final static String PREFERENCES = "AlarmClock"; - final static String PREF_CLOCK_FACE = "face"; - final static String PREF_SHOW_CLOCK = "show_clock"; /** Cap alarm count at this number */ final static int MAX_ALARM_COUNT = 12; @@ -66,29 +64,11 @@ public class AlarmClock extends Activity implements OnItemClickListener { private SharedPreferences mPrefs; private LayoutInflater mFactory; - private ViewGroup mClockLayout; - private View mClock = null; private ListView mAlarmsList; private Cursor mCursor; private String mAm, mPm; - /** - * Which clock face to show - */ - private int mFace = -1; - - /* - * FIXME: it would be nice for this to live in an xml config file. - */ - final static int[] CLOCKS = { - R.layout.clock_basic_bw, - R.layout.clock_googly, - R.layout.clock_droid2, - R.layout.clock_droids, - R.layout.digital_clock - }; - private class AlarmTimeAdapter extends CursorAdapter { public AlarmTimeAdapter(Context context, Cursor cursor) { super(context, cursor); @@ -208,14 +188,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { mCursor = Alarms.getAlarmsCursor(getContentResolver()); updateLayout(); - setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - updateLayout(); - inflateClock(); } private void updateLayout() { @@ -225,31 +197,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { mAlarmsList.setVerticalScrollBarEnabled(true); mAlarmsList.setOnItemClickListener(this); mAlarmsList.setOnCreateContextMenuListener(this); - - mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); - mClockLayout.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - final Intent intent = - new Intent(AlarmClock.this, ClockPicker.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - - int face = mPrefs.getInt(PREF_CLOCK_FACE, 0); - if (mFace != face) { - if (face < 0 || face >= AlarmClock.CLOCKS.length) { - mFace = 0; - } else { - mFace = face; - } - inflateClock(); - } } @Override @@ -259,25 +206,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { mCursor.deactivate(); } - protected void inflateClock() { - if (mClock != null) { - mClockLayout.removeView(mClock); - } - - LayoutInflater.from(this).inflate(CLOCKS[mFace], mClockLayout); - mClock = findViewById(R.id.clock); - - TextView am = (TextView) findViewById(R.id.am); - TextView pm = (TextView) findViewById(R.id.pm); - - if (am != null) { - am.setText(mAm); - } - if (pm != null) { - pm.setText(mPm); - } - } - @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate our menu. @@ -333,9 +261,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { public boolean onPrepareOptionsMenu(Menu menu) { menu.findItem(R.id.menu_add_alarm).setVisible( mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); - menu.findItem(R.id.menu_toggle_clock).setTitle( - getClockVisibility() ? R.string.hide_clock - : R.string.show_clock); return super.onPrepareOptionsMenu(menu); } @@ -355,11 +280,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { startActivity(intent); return true; - case R.id.menu_toggle_clock: - setClockVisibility(!getClockVisibility()); - saveClockVisibility(); - return true; - case R.id.menu_settings: startActivity(new Intent(this, SettingsActivity.class)); return true; @@ -367,17 +287,4 @@ public class AlarmClock extends Activity implements OnItemClickListener { return super.onOptionsItemSelected(item); } - - - private boolean getClockVisibility() { - return mClockLayout.getVisibility() == View.VISIBLE; - } - - private void setClockVisibility(boolean visible) { - mClockLayout.setVisibility(visible ? View.VISIBLE : View.GONE); - } - - private void saveClockVisibility() { - mPrefs.edit().putBoolean(PREF_SHOW_CLOCK, getClockVisibility()).commit(); - } } diff --git a/src/com/android/deskclock/ClockPicker.java b/src/com/android/deskclock/ClockPicker.java deleted file mode 100644 index 077eba1ef..000000000 --- a/src/com/android/deskclock/ClockPicker.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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.deskclock; - -import android.app.Activity; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.Gallery; - -/** - * Clock face picker for the Alarm Clock application. - */ -public class ClockPicker extends Activity implements - AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener { - - private LayoutInflater mFactory; - private Gallery mGallery; - - private SharedPreferences mPrefs; - private View mClock; - private ViewGroup mClockLayout; - private int mPosition; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - requestWindowFeature(Window.FEATURE_NO_TITLE); - - mFactory = LayoutInflater.from(this); - setContentView(R.layout.clockpicker); - - mGallery = (Gallery) findViewById(R.id.gallery); - mGallery.setAdapter(new ClockAdapter()); - mGallery.setOnItemSelectedListener(this); - mGallery.setOnItemClickListener(this); - - mPrefs = getSharedPreferences(AlarmClock.PREFERENCES, 0); - int face = mPrefs.getInt(AlarmClock.PREF_CLOCK_FACE, 0); - if (face < 0 || face >= AlarmClock.CLOCKS.length) face = 0; - - mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); - mClockLayout.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - selectClock(mPosition); - } - }); - - mGallery.setSelection(face, false); - } - - public void onItemSelected(AdapterView parent, View v, int position, long id) { - if (mClock != null) { - mClockLayout.removeView(mClock); - } - mClock = mFactory.inflate(AlarmClock.CLOCKS[position], null); - mClockLayout.addView(mClock, 0); - mPosition = position; - } - - public void onItemClick(AdapterView parent, View v, int position, long id) { - selectClock(position); - } - - private synchronized void selectClock(int position) { - SharedPreferences.Editor ed = mPrefs.edit(); - ed.putInt(AlarmClock.PREF_CLOCK_FACE, position); - ed.commit(); - - setResult(RESULT_OK); - finish(); - } - - public void onNothingSelected(AdapterView parent) { - } - - class ClockAdapter extends BaseAdapter { - - public ClockAdapter() { - } - - public int getCount() { - return AlarmClock.CLOCKS.length; - } - - public Object getItem(int position) { - return position; - } - - public long getItemId(int position) { - return position; - } - - public View getView(final int position, View convertView, ViewGroup parent) { - View clock = mFactory.inflate(AlarmClock.CLOCKS[position], null); - return clock; - } - - } -} diff --git a/src/com/android/deskclock/DigitalClock.java b/src/com/android/deskclock/DigitalClock.java index 14205e7ae..d58154ccb 100644 --- a/src/com/android/deskclock/DigitalClock.java +++ b/src/com/android/deskclock/DigitalClock.java @@ -22,8 +22,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.database.ContentObserver; -import android.graphics.drawable.AnimationDrawable; -import android.graphics.drawable.Drawable; import android.os.Handler; import android.provider.Settings; import android.text.format.DateFormat; @@ -45,7 +43,6 @@ public class DigitalClock extends LinearLayout { private String mFormat; private TextView mTimeDisplay; private AmPm mAmPm; - private boolean mAnimate; private ContentObserver mFormatChangeObserver; private boolean mLive = true; private boolean mAttached; @@ -128,12 +125,6 @@ public class DigitalClock extends LinearLayout { if (mAttached) return; mAttached = true; - if (mAnimate) { - setBackgroundResource(R.drawable.animate_circle); - /* Start the animation (looped playback by default). */ - ((AnimationDrawable) getBackground()).start(); - } - if (mLive) { /* monitor time ticks, time changed, timezone */ IntentFilter filter = new IntentFilter(); @@ -158,11 +149,6 @@ public class DigitalClock extends LinearLayout { if (!mAttached) return; mAttached = false; - Drawable background = getBackground(); - if (background instanceof AnimationDrawable) { - ((AnimationDrawable) background).stop(); - } - if (mLive) { mContext.unregisterReceiver(mIntentReceiver); } @@ -191,10 +177,6 @@ public class DigitalClock extends LinearLayout { mAmPm.setShowAmPm(mFormat == M12); } - void setAnimate() { - mAnimate = true; - } - void setLive(boolean live) { mLive = live; } -- cgit v1.2.3 From 93e942a97a3aba0ad68be6b3db181c939199fa2e Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Mon, 19 Oct 2009 16:15:39 -0400 Subject: Change the default alarms. 8:30 am MTWThF 9:00 am SatSun --- src/com/android/deskclock/AlarmProvider.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmProvider.java b/src/com/android/deskclock/AlarmProvider.java index c635b0113..4b6c8a390 100644 --- a/src/com/android/deskclock/AlarmProvider.java +++ b/src/com/android/deskclock/AlarmProvider.java @@ -67,9 +67,8 @@ public class AlarmProvider extends ContentProvider { String insertMe = "INSERT INTO alarms " + "(hour, minutes, daysofweek, alarmtime, enabled, vibrate, message, alert) " + "VALUES "; - db.execSQL(insertMe + "(7, 0, 127, 0, 0, 1, '', '');"); db.execSQL(insertMe + "(8, 30, 31, 0, 0, 1, '', '');"); - db.execSQL(insertMe + "(9, 00, 0, 0, 0, 1, '', '');"); + db.execSQL(insertMe + "(9, 00, 96, 0, 0, 1, '', '');"); } @Override -- cgit v1.2.3 From 52b3d633a031c64dcefefc4884b9cea5781425c2 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 20 Oct 2009 11:10:33 -0400 Subject: Use Clockopia as the typeface for the digital clock. --- src/com/android/deskclock/DigitalClock.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DigitalClock.java b/src/com/android/deskclock/DigitalClock.java index d58154ccb..c4a5243b6 100644 --- a/src/com/android/deskclock/DigitalClock.java +++ b/src/com/android/deskclock/DigitalClock.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.database.ContentObserver; +import android.graphics.Typeface; import android.os.Handler; import android.provider.Settings; import android.text.format.DateFormat; @@ -66,10 +67,12 @@ public class DigitalClock extends LinearLayout { private LinearLayout mAmPmLayout; private TextView mAm, mPm; - AmPm(View parent) { + AmPm(View parent, Typeface tf) { mAmPmLayout = (LinearLayout) parent.findViewById(R.id.am_pm); mAm = (TextView)mAmPmLayout.findViewById(R.id.am); + mAm.setTypeface(tf); mPm = (TextView)mAmPmLayout.findViewById(R.id.pm); + mPm.setTypeface(tf); Resources r = parent.getResources(); mColorOn = r.getColor(R.color.ampm_on); @@ -109,8 +112,11 @@ public class DigitalClock extends LinearLayout { protected void onFinishInflate() { super.onFinishInflate(); + Typeface tf = Typeface.createFromAsset(getContext().getAssets(), + "fonts/Clockopia.ttf"); mTimeDisplay = (TextView) findViewById(R.id.timeDisplay); - mAmPm = new AmPm(this); + mTimeDisplay.setTypeface(tf); + mAmPm = new AmPm(this, tf); mCalendar = Calendar.getInstance(); setDateFormat(); -- cgit v1.2.3 From dce90517954050428bfaf20e1409c10d372a4759 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 20 Oct 2009 16:30:55 -0400 Subject: Remove the menu and replace with buttons. Change the layout to allow two buttons at the bottom of the list. Update the "Add alarm" button when adding/removing alarms to change enabled state. --- src/com/android/deskclock/AlarmClock.java | 89 +++++++++++++++---------------- 1 file changed, 44 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java index 4b339d190..c32c04675 100644 --- a/src/com/android/deskclock/AlarmClock.java +++ b/src/com/android/deskclock/AlarmClock.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; +import android.database.DataSetObserver; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -40,10 +41,11 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; +import android.widget.Button; +import android.widget.CheckBox; import android.widget.CursorAdapter; import android.widget.ListView; import android.widget.TextView; -import android.widget.CheckBox; import java.util.Calendar; import java.text.DateFormatSymbols; @@ -193,10 +195,50 @@ public class AlarmClock extends Activity implements OnItemClickListener { private void updateLayout() { setContentView(R.layout.alarm_clock); mAlarmsList = (ListView) findViewById(R.id.alarms_list); - mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); + AlarmTimeAdapter adapter = new AlarmTimeAdapter(this, mCursor); + mAlarmsList.setAdapter(adapter); mAlarmsList.setVerticalScrollBarEnabled(true); mAlarmsList.setOnItemClickListener(this); mAlarmsList.setOnCreateContextMenuListener(this); + + final Button addAlarm = (Button) findViewById(R.id.add_alarm); + addAlarm.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Uri uri = Alarms.addAlarm(getContentResolver()); + // FIXME: scroll to new item? + String segment = uri.getPathSegments().get(1); + int newId = Integer.parseInt(segment); + if (Log.LOGV) { + Log.v("In AlarmClock, new alarm id = " + newId); + } + Intent intent = + new Intent(AlarmClock.this, SetAlarm.class); + intent.putExtra(Alarms.ALARM_ID, newId); + startActivity(intent); + } + }); + // Update the enabled state of the "Add alarm" menu item depending on + // how many alarms are set. + adapter.registerDataSetObserver(new DataSetObserver() { + public void onChanged() { + updateAddAlarmButton(addAlarm); + } + public void onInvalidate() { + updateAddAlarmButton(addAlarm); + } + }); + + Button settings = (Button) findViewById(R.id.settings); + settings.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + startActivity( + new Intent(AlarmClock.this, SettingsActivity.class)); + } + }); + } + + private void updateAddAlarmButton(Button b) { + b.setEnabled(mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); } @Override @@ -206,14 +248,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { mCursor.deactivate(); } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate our menu. - getMenuInflater().inflate(R.menu.main_menu, menu); - - return super.onCreateOptionsMenu(menu); - } - @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { @@ -252,39 +286,4 @@ public class AlarmClock extends Activity implements OnItemClickListener { intent.putExtra(Alarms.ALARM_ID, (int) id); startActivity(intent); } - - /** - * Only allow user to add a new alarm if there are fewer than - * MAX_ALARM_COUNT - */ - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.findItem(R.id.menu_add_alarm).setVisible( - mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_add_alarm: - Uri uri = Alarms.addAlarm(getContentResolver()); - // FIXME: scroll to new item? - String segment = uri.getPathSegments().get(1); - int newId = Integer.parseInt(segment); - if (Log.LOGV) { - Log.v("In AlarmClock, new alarm id = " + newId); - } - Intent intent = new Intent(this, SetAlarm.class); - intent.putExtra(Alarms.ALARM_ID, newId); - startActivity(intent); - return true; - - case R.id.menu_settings: - startActivity(new Intent(this, SettingsActivity.class)); - return true; - } - - return super.onOptionsItemSelected(item); - } } -- cgit v1.2.3 From f8317ad7146b1cdfeded7acb23ae8f770b2d3f97 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Tue, 20 Oct 2009 16:51:32 -0400 Subject: New DeskClock activity, now the main app entry point. UI not yet wired up for the most part, but Alarms should take you to the AlarmClock activity, and there's a functional implementation of screen dimming ("nightstand mode"). --- src/com/android/deskclock/DeskClock.java | 141 +++++++++++++++++++++++ src/com/android/deskclock/DockEventReceiver.java | 2 +- 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/com/android/deskclock/DeskClock.java (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java new file mode 100644 index 000000000..3dce92117 --- /dev/null +++ b/src/com/android/deskclock/DeskClock.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2009 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.deskclock; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.text.TextUtils; +import android.view.animation.AnimationUtils; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.Button; +import android.widget.TextView; +import android.widget.CheckBox; + +import java.util.Calendar; +import java.text.DateFormatSymbols; + +/** + * DeskClock clock view for desk docks. + */ +public class DeskClock extends Activity { + + private TextView mNextAlarm = null; + private Drawable mAlarmIcon = null; + + private TextView mDate; + private DigitalClock mTime; + + private boolean mDimmed = false; + + private void refreshAlarm() { + String nextAlarm = Settings.System.getString(getContentResolver(), + Settings.System.NEXT_ALARM_FORMATTED); + if (nextAlarm != null && TextUtils.isEmpty(nextAlarm)) { + mNextAlarm.setText(nextAlarm); + mAlarmIcon = getResources().getDrawable(android.R.drawable.ic_lock_idle_alarm); + mNextAlarm.setCompoundDrawablesWithIntrinsicBounds( + mAlarmIcon, null, null, null); + mNextAlarm.setVisibility(View.VISIBLE); + } else { + mNextAlarm.setVisibility(View.INVISIBLE); + } + } + + private void doDim() { + View tintView = findViewById(R.id.window_tint); + + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + if (mDimmed) { + winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + tintView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.dim)); + } else { + winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); + tintView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.undim)); + } + + win.setAttributes(winParams); + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setContentView(R.layout.desk_clock); + + mTime = (DigitalClock) findViewById(R.id.time); + mDate = (TextView) findViewById(R.id.date); + + mNextAlarm = (TextView) findViewById(R.id.nextAlarm); + + final Button alarmButton = (Button) findViewById(R.id.alarm_button); + alarmButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + startActivity(new Intent(DeskClock.this, AlarmClock.class)); + } + }); + + final Button galleryButton = (Button) findViewById(R.id.gallery_button); + galleryButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + } + }); + + final Button musicButton = (Button) findViewById(R.id.music_button); + musicButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + } + }); + + final Button nightmodeButton = (Button) findViewById(R.id.nightmode_button); + nightmodeButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mDimmed = ! mDimmed; + doDim(); + } + }); + + doDim(); + refreshAlarm(); + } + +} diff --git a/src/com/android/deskclock/DockEventReceiver.java b/src/com/android/deskclock/DockEventReceiver.java index e15e2cbfd..bfc4673d9 100644 --- a/src/com/android/deskclock/DockEventReceiver.java +++ b/src/com/android/deskclock/DockEventReceiver.java @@ -37,7 +37,7 @@ public class DockEventReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { Intent clockIntent = new Intent(Intent.ACTION_MAIN); clockIntent.setComponent( - new ComponentName(context, AlarmClock.class)); + new ComponentName(context, DeskClock.class)); clockIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); String action = intent.getAction(); -- cgit v1.2.3 From da3b21c3fb5227cfabe7fefa777470d7a4604bb0 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Thu, 22 Oct 2009 11:13:13 -0400 Subject: Show AM/PM in the same spot on the clock. Make the alarm list, main screen, and alert consistent. Have DigitalClock deal with the AM/PM strings. Put a little padding above and below the clock in the alert. --- src/com/android/deskclock/AlarmClock.java | 10 ---------- src/com/android/deskclock/DigitalClock.java | 27 +++++++++++---------------- 2 files changed, 11 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java index c32c04675..e9d5e7866 100644 --- a/src/com/android/deskclock/AlarmClock.java +++ b/src/com/android/deskclock/AlarmClock.java @@ -48,7 +48,6 @@ import android.widget.ListView; import android.widget.TextView; import java.util.Calendar; -import java.text.DateFormatSymbols; /** * AlarmClock application. @@ -69,8 +68,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { private ListView mAlarmsList; private Cursor mCursor; - private String mAm, mPm; - private class AlarmTimeAdapter extends CursorAdapter { public AlarmTimeAdapter(Context context, Cursor cursor) { super(context, cursor); @@ -79,9 +76,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { public View newView(Context context, Cursor cursor, ViewGroup parent) { View ret = mFactory.inflate(R.layout.alarm_time, parent, false); - ((TextView) ret.findViewById(R.id.am)).setText(mAm); - ((TextView) ret.findViewById(R.id.pm)).setText(mPm); - DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); digitalClock.setLive(false); if (Log.LOGV) Log.v("newView " + cursor.getPosition()); @@ -181,10 +175,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { protected void onCreate(Bundle icicle) { super.onCreate(icicle); - String[] ampm = new DateFormatSymbols().getAmPmStrings(); - mAm = ampm[0]; - mPm = ampm[1]; - mFactory = LayoutInflater.from(this); mPrefs = getSharedPreferences(PREFERENCES, 0); mCursor = Alarms.getAlarmsCursor(getContentResolver()); diff --git a/src/com/android/deskclock/DigitalClock.java b/src/com/android/deskclock/DigitalClock.java index c4a5243b6..d28cc7de7 100644 --- a/src/com/android/deskclock/DigitalClock.java +++ b/src/com/android/deskclock/DigitalClock.java @@ -31,6 +31,7 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import java.text.DateFormatSymbols; import java.util.Calendar; /** @@ -62,30 +63,24 @@ public class DigitalClock extends LinearLayout { }; static class AmPm { - private int mColorOn, mColorOff; - - private LinearLayout mAmPmLayout; - private TextView mAm, mPm; + private TextView mAmPm; + private String mAmString, mPmString; AmPm(View parent, Typeface tf) { - mAmPmLayout = (LinearLayout) parent.findViewById(R.id.am_pm); - mAm = (TextView)mAmPmLayout.findViewById(R.id.am); - mAm.setTypeface(tf); - mPm = (TextView)mAmPmLayout.findViewById(R.id.pm); - mPm.setTypeface(tf); - - Resources r = parent.getResources(); - mColorOn = r.getColor(R.color.ampm_on); - mColorOff = r.getColor(R.color.ampm_off); + mAmPm = (TextView) parent.findViewById(R.id.am_pm); + mAmPm.setTypeface(tf); + + String[] ampm = new DateFormatSymbols().getAmPmStrings(); + mAmString = ampm[0]; + mPmString = ampm[1]; } void setShowAmPm(boolean show) { - mAmPmLayout.setVisibility(show ? View.VISIBLE : View.GONE); + mAmPm.setVisibility(show ? View.VISIBLE : View.GONE); } void setIsMorning(boolean isMorning) { - mAm.setTextColor(isMorning ? mColorOn : mColorOff); - mPm.setTextColor(isMorning ? mColorOff : mColorOn); + mAmPm.setText(isMorning ? mAmString : mPmString); } } -- cgit v1.2.3 From ef18177f9bbbeab8ef266c3f080175bc813dc34c Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Thu, 22 Oct 2009 09:42:27 -0400 Subject: Wire up date & next alarm displays. Change-Id: I367e0dcdb3fdfa7256dfd5ce54097c8206806f51 --- src/com/android/deskclock/DeskClock.java | 54 ++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 3dce92117..e756db14d 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -18,9 +18,11 @@ package com.android.deskclock; import android.app.Activity; import android.app.AlertDialog; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; @@ -50,8 +52,8 @@ import android.widget.Button; import android.widget.TextView; import android.widget.CheckBox; -import java.util.Calendar; -import java.text.DateFormatSymbols; +import java.text.DateFormat; +import java.util.Date; /** * DeskClock clock view for desk docks. @@ -59,21 +61,32 @@ import java.text.DateFormatSymbols; public class DeskClock extends Activity { private TextView mNextAlarm = null; - private Drawable mAlarmIcon = null; - private TextView mDate; private DigitalClock mTime; private boolean mDimmed = false; + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + refreshDate(); + } + }; + + + private DateFormat mDateFormat; + + private void refreshDate() { + mDate.setText(mDateFormat.format(new Date())); + } + private void refreshAlarm() { String nextAlarm = Settings.System.getString(getContentResolver(), Settings.System.NEXT_ALARM_FORMATTED); - if (nextAlarm != null && TextUtils.isEmpty(nextAlarm)) { + if (!TextUtils.isEmpty(nextAlarm)) { mNextAlarm.setText(nextAlarm); - mAlarmIcon = getResources().getDrawable(android.R.drawable.ic_lock_idle_alarm); - mNextAlarm.setCompoundDrawablesWithIntrinsicBounds( - mAlarmIcon, null, null, null); + //mNextAlarm.setCompoundDrawablesWithIntrinsicBounds( + // android.R.drawable.ic_lock_idle_alarm, 0, 0, 0); mNextAlarm.setVisibility(View.VISIBLE); } else { mNextAlarm.setVisibility(View.INVISIBLE); @@ -96,6 +109,29 @@ public class DeskClock extends Activity { win.setAttributes(winParams); } + @Override + public void onResume() { + super.onResume(); + + // reload the date format in case the user has changed settings + // recently + mDateFormat = java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DATE_CHANGED); + registerReceiver(mIntentReceiver, filter); + + doDim(); + refreshDate(); + refreshAlarm(); + } + + @Override + public void onPause() { + super.onPause(); + unregisterReceiver(mIntentReceiver); + } + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -134,8 +170,6 @@ public class DeskClock extends Activity { } }); - doDim(); - refreshAlarm(); } } -- cgit v1.2.3 From 3955e05df4f0b1fa223ccc1cf8a44d5b5d5bd8a0 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 23 Oct 2009 13:41:19 -0400 Subject: UI improvements to DeskClock. Fixes: - "Dim" button moved to TR corner per spec. - Next Alarm moved to TL corner per spec. - Launcher button added (not yet wired). - Battery indicator added to BR corner per spec. - Drop shadow added to all text per spec. - Layout now fills the portrait display vertically. Known problems: - No landscape layout - Layout jumps when status bar disappears for dim mode - No artwork yet - Weather is a big orange box instead of, you know, weather --- src/com/android/deskclock/DeskClock.java | 56 ++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index e756db14d..8ff691428 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -52,6 +52,10 @@ import android.widget.Button; import android.widget.TextView; import android.widget.CheckBox; +import static android.os.BatteryManager.BATTERY_STATUS_CHARGING; +import static android.os.BatteryManager.BATTERY_STATUS_FULL; +import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; + import java.text.DateFormat; import java.util.Date; @@ -62,6 +66,7 @@ public class DeskClock extends Activity { private TextView mNextAlarm = null; private TextView mDate; + private TextView mBatteryDisplay; private DigitalClock mTime; private boolean mDimmed = false; @@ -69,12 +74,44 @@ public class DeskClock extends Activity { private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - refreshDate(); + final String action = intent.getAction(); + if (Intent.ACTION_DATE_CHANGED.equals(action)) { + refreshDate(); + } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { + handleBatteryUpdate( + intent.getIntExtra("status", BATTERY_STATUS_UNKNOWN), + intent.getIntExtra("level", 0)); + } } }; private DateFormat mDateFormat; + + private int mBatteryLevel; + private boolean mPluggedIn; + + // Adapted from KeyguardUpdateMonitor.java + private void handleBatteryUpdate(int plugStatus, int batteryLevel) { + final boolean pluggedIn = (plugStatus == BATTERY_STATUS_CHARGING || plugStatus == BATTERY_STATUS_FULL); + if (pluggedIn != mPluggedIn || batteryLevel != mBatteryLevel) { + mBatteryLevel = batteryLevel; + mPluggedIn = pluggedIn; + refreshBattery(); + } + } + + private void refreshBattery() { + if (mPluggedIn /* || mBatteryLevel < LOW_BATTERY_THRESHOLD */) { + mBatteryDisplay.setCompoundDrawablesWithIntrinsicBounds( + 0, 0, android.R.drawable.ic_lock_idle_charging, 0); + mBatteryDisplay.setText( + getString(R.string.battery_charging_level, mBatteryLevel)); + mBatteryDisplay.setVisibility(View.VISIBLE); + } else { + mBatteryDisplay.setVisibility(View.INVISIBLE); + } + } private void refreshDate() { mDate.setText(mDateFormat.format(new Date())); @@ -98,14 +135,26 @@ public class DeskClock extends Activity { Window win = getWindow(); WindowManager.LayoutParams winParams = win.getAttributes(); + + // dim the wallpaper somewhat (how much is determined below) + winParams.flags |= (WindowManager.LayoutParams.FLAG_DIM_BEHIND); + if (mDimmed) { winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; +// winParams.flags &= (~WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + winParams.dimAmount = 0.5f; // pump up contrast in dim mode + + // show the window tint tintView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.dim)); } else { winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); +// winParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + winParams.dimAmount = 0.2f; // lower contrast in normal mode + + // hide the window tint tintView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.undim)); } - + win.setAttributes(winParams); } @@ -119,11 +168,13 @@ public class DeskClock extends Activity { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_DATE_CHANGED); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); registerReceiver(mIntentReceiver, filter); doDim(); refreshDate(); refreshAlarm(); + refreshBattery(); } @Override @@ -140,6 +191,7 @@ public class DeskClock extends Activity { mTime = (DigitalClock) findViewById(R.id.time); mDate = (TextView) findViewById(R.id.date); + mBatteryDisplay = (TextView) findViewById(R.id.battery); mNextAlarm = (TextView) findViewById(R.id.nextAlarm); -- cgit v1.2.3 From 72a353a7d443f17a0e16412ae85ad359d97e9394 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 23 Oct 2009 16:11:08 -0400 Subject: Wiring up gallery & music buttons. --- src/com/android/deskclock/DeskClock.java | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 8ff691428..6770aa9c3 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -33,6 +33,7 @@ import android.os.Bundle; import android.os.Handler; import android.provider.Settings; import android.text.TextUtils; +import android.util.Log; import android.view.animation.AnimationUtils; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -64,6 +65,10 @@ import java.util.Date; */ public class DeskClock extends Activity { + private static final String LOG_TAG = "DeskClock"; + + private static final String MUSIC_NOW_PLAYING_ACTIVITY = "com.android.music.PLAYBACK_VIEWER"; + private TextView mNextAlarm = null; private TextView mDate; private TextView mBatteryDisplay; @@ -85,7 +90,6 @@ public class DeskClock extends Activity { } }; - private DateFormat mDateFormat; private int mBatteryLevel; @@ -205,12 +209,33 @@ public class DeskClock extends Activity { final Button galleryButton = (Button) findViewById(R.id.gallery_button); galleryButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { + try { + startActivity(new Intent( + Intent.ACTION_VIEW, + android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI)); + } catch (android.content.ActivityNotFoundException e) { + Log.e(LOG_TAG, "Couldn't launch image browser", e); + } } }); final Button musicButton = (Button) findViewById(R.id.music_button); musicButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { + try { + startActivity(new Intent(MUSIC_NOW_PLAYING_ACTIVITY)); + } catch (android.content.ActivityNotFoundException e) { + Log.e(LOG_TAG, "Couldn't launch music browser", e); + } + } + }); + + final Button homeButton = (Button) findViewById(R.id.home_button); + homeButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + startActivity( + new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME)); } }); -- cgit v1.2.3 From d13733225cb1a3e16413b35336e94e400bf5d399 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Tue, 27 Oct 2009 09:46:05 -0400 Subject: Landscape layout for DeskClock. Created several new sub-layouts that are shared between the two orientations. Known issue: the activity currently re-starts (forgetting the dim state) between orientations. --- src/com/android/deskclock/DeskClock.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 6770aa9c3..a451372a5 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -187,10 +187,8 @@ public class DeskClock extends Activity { unregisterReceiver(mIntentReceiver); } - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); + private void initViews() { setContentView(R.layout.desk_clock); mTime = (DigitalClock) findViewById(R.id.time); @@ -247,6 +245,20 @@ public class DeskClock extends Activity { } }); + doDim(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + initViews(); + } + + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + initViews(); } } -- cgit v1.2.3 From a21b2db5541b0bf20582572e4f072137ff46c69f Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Wed, 28 Oct 2009 12:36:06 -0400 Subject: The snooze notification must be clicked to be cleared. Change all the notifications to ongoing. This means they cannot be cleared unless clicked. If the alarm is killed after 10 minutes, the notification is changed to a vanilla notification and can be cleared with the "Clear All" button. This change will be merged to the MR2 branch of AlarmClock. Bug: 2220016 --- src/com/android/deskclock/AlarmAlert.java | 4 ++-- src/com/android/deskclock/AlarmReceiver.java | 21 +++++++-------------- src/com/android/deskclock/Alarms.java | 3 --- 3 files changed, 9 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmAlert.java b/src/com/android/deskclock/AlarmAlert.java index 8abbd7183..6319dff70 100644 --- a/src/com/android/deskclock/AlarmAlert.java +++ b/src/com/android/deskclock/AlarmAlert.java @@ -158,8 +158,8 @@ public class AlarmAlert extends Activity { n.setLatestEventInfo(this, label, getString(R.string.alarm_notify_snooze_text, Alarms.formatTime(this, c)), broadcast); - n.deleteIntent = broadcast; - n.flags |= Notification.FLAG_AUTO_CANCEL; + n.flags |= Notification.FLAG_AUTO_CANCEL + | Notification.FLAG_ONGOING_EVENT; nm.notify(mAlarm.id, n); String displayTime = getString(R.string.alarm_alert_snooze_set, diff --git a/src/com/android/deskclock/AlarmReceiver.java b/src/com/android/deskclock/AlarmReceiver.java index 18457b65f..92f929a67 100644 --- a/src/com/android/deskclock/AlarmReceiver.java +++ b/src/com/android/deskclock/AlarmReceiver.java @@ -42,13 +42,7 @@ public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - // Take care of the easy intents first. - if (Alarms.CLEAR_NOTIFICATION.equals(intent.getAction())) { - // If this is the "Clear All Notifications" intent, stop the alarm - // service and return. - context.stopService(new Intent(Alarms.ALARM_ALERT_ACTION)); - return; - } else if (Alarms.ALARM_KILLED.equals(intent.getAction())) { + if (Alarms.ALARM_KILLED.equals(intent.getAction())) { // The alarm has been killed, update the notification updateNotification(context, (Alarm) intent.getParcelableExtra(Alarms.ALARM_INTENT_EXTRA), @@ -149,17 +143,12 @@ public class AlarmReceiver extends BroadcastReceiver { n.setLatestEventInfo(context, label, context.getString(R.string.alarm_notify_text), pendingNotify); - n.flags |= Notification.FLAG_SHOW_LIGHTS; + n.flags |= Notification.FLAG_SHOW_LIGHTS + | Notification.FLAG_ONGOING_EVENT; n.ledARGB = 0xFF00FF00; n.ledOnMS = 500; n.ledOffMS = 500; - // Set the deleteIntent for when the user clicks "Clear All - // Notifications" - Intent clearAll = new Intent(context, AlarmReceiver.class); - clearAll.setAction(Alarms.CLEAR_NOTIFICATION); - n.deleteIntent = PendingIntent.getBroadcast(context, 0, clearAll, 0); - // Send the notification using the alarm id to easily identify the // correct notification. NotificationManager nm = getNotificationManager(context); @@ -197,6 +186,10 @@ public class AlarmReceiver extends BroadcastReceiver { context.getString(R.string.alarm_alert_alert_silenced, timeout), intent); n.flags |= Notification.FLAG_AUTO_CANCEL; + // We have to cancel the original notification since it is in the + // ongoing section and we want the "killed" notification to be a plain + // notification. + nm.cancel(alarm.id); nm.notify(alarm.id, n); } } diff --git a/src/com/android/deskclock/Alarms.java b/src/com/android/deskclock/Alarms.java index cd47b1805..b6a22f4f7 100644 --- a/src/com/android/deskclock/Alarms.java +++ b/src/com/android/deskclock/Alarms.java @@ -43,9 +43,6 @@ public class Alarms { // from the alarm manager. public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT"; - // This is a private action used when the user clears all notifications. - public static final String CLEAR_NOTIFICATION = "clear_notification"; - // This is a private action used by the AlarmKlaxon to update the UI to // show the alarm has been killed. public static final String ALARM_KILLED = "alarm_killed"; -- cgit v1.2.3 From 2763ab4cb6ee8d186eb8894ad006f699bfca3413 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Tue, 27 Oct 2009 16:46:57 -0400 Subject: Show weather forecast in DeskClock. Currently querying the GenieWidget; if it's not present, no weather is shown or even hinted at in the UI. If GenieWidget is available but fails to respond to the query, we show "Weather unavailable." (needs i18n) Note that Genie's WeatherProvider is currently broken, so for now you'll always see "Weather unavailable." Other changes: - Fix dimming/flashing on rotation. - Fix other layout problems & inefficiencies. --- src/com/android/deskclock/DeskClock.java | 195 +++++++++++++++++++++++++++---- 1 file changed, 172 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index a451372a5..cb544d335 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -24,39 +24,46 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.content.res.Resources; import android.database.Cursor; -import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; -import android.view.animation.AnimationUtils; -import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; +import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; -import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnCreateContextMenuListener; +import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.widget.AdapterView; +import android.view.animation.AnimationUtils; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView; import android.widget.Button; -import android.widget.TextView; import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; import static android.os.BatteryManager.BATTERY_STATUS_CHARGING; import static android.os.BatteryManager.BATTERY_STATUS_FULL; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; +import java.io.IOException; +import java.io.InputStream; import java.text.DateFormat; import java.util.Date; @@ -64,18 +71,54 @@ import java.util.Date; * DeskClock clock view for desk docks. */ public class DeskClock extends Activity { + private static final boolean DEBUG = true; private static final String LOG_TAG = "DeskClock"; private static final String MUSIC_NOW_PLAYING_ACTIVITY = "com.android.music.PLAYBACK_VIEWER"; - private TextView mNextAlarm = null; + private final long FETCH_WEATHER_DELAY = 5 * 60 * 1000; // 5 min. + private final int FETCH_WEATHER_DATA_MSG = 10000; + private final int UPDATE_WEATHER_DISPLAY_MSG = 10001; + + private static final String GENIE_PACKAGE_ID = "com.google.android.apps.genie.geniewidget"; + private static final String WEATHER_CONTENT_AUTHORITY = GENIE_PACKAGE_ID + ".weather"; + private static final String WEATHER_CONTENT_PATH = "/weather/current"; + private static final String[] WEATHER_CONTENT_COLUMNS = new String[] { + "location", + "timestamp", + "highTemperature", + "lowTemperature", + "iconUrl", + "iconResId", + "description", + }; + + private DigitalClock mTime; private TextView mDate; + + private TextView mNextAlarm = null; private TextView mBatteryDisplay; - private DigitalClock mTime; + + private TextView mWeatherTemperature; + private TextView mWeatherLocation; + private ImageView mWeatherIcon; + + private String mWeatherTemperatureString; + private String mWeatherLocationString; + private Drawable mWeatherIconDrawable; + + private Resources mGenieResources = null; private boolean mDimmed = false; + private DateFormat mDateFormat; + + private int mBatteryLevel; + private boolean mPluggedIn; + + private boolean mWeatherFetchScheduled = false; + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -90,10 +133,95 @@ public class DeskClock extends Activity { } }; - private DateFormat mDateFormat; - - private int mBatteryLevel; - private boolean mPluggedIn; + private final Handler mHandy = new Handler() { + @Override + public void handleMessage(Message m) { + if (DEBUG) Log.d(LOG_TAG, "handleMessage: " + m.toString()); + + if (m.what == FETCH_WEATHER_DATA_MSG) { + if (!mWeatherFetchScheduled) return; + mWeatherFetchScheduled = false; + new Thread() { public void run() { fetchWeatherData(); } }.start(); + scheduleWeatherFetchDelayed(FETCH_WEATHER_DELAY); + } else if (m.what == UPDATE_WEATHER_DISPLAY_MSG) { + updateWeatherDisplay(); + } + } + }; + + private boolean supportsWeather() { + return (mGenieResources != null); + } + + private void scheduleWeatherFetchDelayed(long delay) { + if (mWeatherFetchScheduled) return; + + if (DEBUG) Log.d(LOG_TAG, "scheduling weather fetch message for " + delay + "ms from now"); + + mWeatherFetchScheduled = true; + + mHandy.sendEmptyMessageDelayed(FETCH_WEATHER_DATA_MSG, delay); + } + + private void unscheduleWeatherFetch() { + mWeatherFetchScheduled = false; + } + + private void fetchWeatherData() { + // if we couldn't load the weather widget's resources, we simply + // assume it's not present on the device. + if (mGenieResources == null) return; + + Uri queryUri = new Uri.Builder() + .scheme(android.content.ContentResolver.SCHEME_CONTENT) + .authority(WEATHER_CONTENT_AUTHORITY) + .path(WEATHER_CONTENT_PATH) + .appendPath(new Long(System.currentTimeMillis()).toString()) + .build(); + + if (DEBUG) Log.d(LOG_TAG, "querying genie: " + queryUri); + + Cursor cur; + try { + cur = managedQuery( + queryUri, + WEATHER_CONTENT_COLUMNS, + null, + null, + null); + } catch (RuntimeException e) { + Log.e(LOG_TAG, "Weather query failed", e); + cur = null; + } + + if (cur != null && cur.moveToFirst()) { + mWeatherIconDrawable = mGenieResources.getDrawable(cur.getInt( + cur.getColumnIndexOrThrow("iconResId"))); + mWeatherTemperatureString = cur.getString( + cur.getColumnIndexOrThrow("highTemperature")); + mWeatherLocationString = cur.getString( + cur.getColumnIndexOrThrow("location")); + } else { + Log.w(LOG_TAG, "No weather information available (cur=" + + cur +")"); + mWeatherIconDrawable = null; + mWeatherTemperatureString = ""; + mWeatherLocationString = "Weather data unavailable."; // TODO: internationalize + } + + mHandy.sendEmptyMessage(UPDATE_WEATHER_DISPLAY_MSG); + } + + private void refreshWeather() { + if (supportsWeather()) + scheduleWeatherFetchDelayed(0); + } + + private void updateWeatherDisplay() { + mWeatherTemperature.setText(mWeatherTemperatureString); + mWeatherLocation.setText(mWeatherLocationString); + mWeatherIcon.setImageDrawable(mWeatherIconDrawable); + } // Adapted from KeyguardUpdateMonitor.java private void handleBatteryUpdate(int plugStatus, int batteryLevel) { @@ -134,7 +262,14 @@ public class DeskClock extends Activity { } } - private void doDim() { + private void refreshAll() { + refreshDate(); + refreshAlarm(); + refreshBattery(); + refreshWeather(); + } + + private void doDim(boolean fade) { View tintView = findViewById(R.id.window_tint); Window win = getWindow(); @@ -149,14 +284,18 @@ public class DeskClock extends Activity { winParams.dimAmount = 0.5f; // pump up contrast in dim mode // show the window tint - tintView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.dim)); + tintView.startAnimation(AnimationUtils.loadAnimation(this, + fade ? R.anim.dim + : R.anim.dim_instant)); } else { winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); // winParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; winParams.dimAmount = 0.2f; // lower contrast in normal mode // hide the window tint - tintView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.undim)); + tintView.startAnimation(AnimationUtils.loadAnimation(this, + fade ? R.anim.undim + : R.anim.undim_instant)); } win.setAttributes(winParams); @@ -175,16 +314,15 @@ public class DeskClock extends Activity { filter.addAction(Intent.ACTION_BATTERY_CHANGED); registerReceiver(mIntentReceiver, filter); - doDim(); - refreshDate(); - refreshAlarm(); - refreshBattery(); + doDim(false); + refreshAll(); } @Override public void onPause() { super.onPause(); unregisterReceiver(mIntentReceiver); + unscheduleWeatherFetch(); } @@ -195,6 +333,10 @@ public class DeskClock extends Activity { mDate = (TextView) findViewById(R.id.date); mBatteryDisplay = (TextView) findViewById(R.id.battery); + mWeatherTemperature = (TextView) findViewById(R.id.weather_temperature); + mWeatherLocation = (TextView) findViewById(R.id.weather_location); + mWeatherIcon = (ImageView) findViewById(R.id.weather_icon); + mNextAlarm = (TextView) findViewById(R.id.nextAlarm); final Button alarmButton = (Button) findViewById(R.id.alarm_button); @@ -241,23 +383,30 @@ public class DeskClock extends Activity { nightmodeButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mDimmed = ! mDimmed; - doDim(); + doDim(true); } }); - - doDim(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); initViews(); + doDim(false); + refreshAll(); } - @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); + + try { + mGenieResources = getPackageManager().getResourcesForApplication(GENIE_PACKAGE_ID); + } catch (PackageManager.NameNotFoundException e) { + // no weather info available + Log.w(LOG_TAG, "Can't find "+GENIE_PACKAGE_ID+". Weather forecast will not be available."); + } + initViews(); } -- cgit v1.2.3 From 91522cd49090f40e2e5b96c9b2e310b1adf1218a Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 30 Oct 2009 14:54:16 -0400 Subject: Completed weather support; new screen-saving mode. Now pulls high and low temperatures, as well as an icon (in the form of a drawable resource), from the Genie widget, periodically. Known issues: artwork is mdpi and fuzzy (bug in Genie). Preliminary support for a new screen-saving mode (subject to change) specifically for OLEDs with short B/R subpixel lifetime. Text is drawn in green on a black background; the text also randomly jumps around the screen. Any user input (touch, trackball/keypress, etc.) will restore the previous view. Screen saver mode kicks in after a delay (or, for debugging, a longpress on "Dim"). Honors the current dimness setting, so if the clock is currently in nightstand mode, the screen saver will be very dim; otherwise (viz., desk mode) the saver will be brighter. --- src/com/android/deskclock/DeskClock.java | 218 +++++++++++++++++++++++++++---- 1 file changed, 196 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index cb544d335..59963a3a5 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -28,6 +28,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -35,8 +36,10 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.os.PowerManager; import android.provider.Settings; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu; @@ -49,7 +52,10 @@ import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; +import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.view.animation.TranslateAnimation; +import android.widget.AbsoluteLayout; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView; @@ -66,6 +72,8 @@ import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; import java.util.Date; +import java.util.Locale; +import java.util.Random; /** * DeskClock clock view for desk docks. @@ -77,9 +85,17 @@ public class DeskClock extends Activity { private static final String MUSIC_NOW_PLAYING_ACTIVITY = "com.android.music.PLAYBACK_VIEWER"; - private final long FETCH_WEATHER_DELAY = 5 * 60 * 1000; // 5 min. - private final int FETCH_WEATHER_DATA_MSG = 10000; - private final int UPDATE_WEATHER_DISPLAY_MSG = 10001; + private final long FETCH_WEATHER_DELAY = 60 * 60 * 1000; // 1 hr + private final long SCREEN_SAVER_TIMEOUT = 5 * 60 * 1000; // 5 min + private final long SCREEN_SAVER_MOVE_DELAY = 5 * 1000; // 15 sec + + private final int FETCH_WEATHER_DATA_MSG = 0x1000; + private final int UPDATE_WEATHER_DISPLAY_MSG = 0x1001; + private final int SCREEN_SAVER_TIMEOUT_MSG = 0x2000; + private final int SCREEN_SAVER_MOVE_MSG = 0x2001; + + private final int SCREEN_SAVER_COLOR = 0xFF008000; + private final int SCREEN_SAVER_COLOR_DIM = 0xFF003000; private static final String GENIE_PACKAGE_ID = "com.google.android.apps.genie.geniewidget"; private static final String WEATHER_CONTENT_AUTHORITY = GENIE_PACKAGE_ID + ".weather"; @@ -100,25 +116,35 @@ public class DeskClock extends Activity { private TextView mNextAlarm = null; private TextView mBatteryDisplay; - private TextView mWeatherTemperature; + private TextView mWeatherHighTemperature; + private TextView mWeatherLowTemperature; private TextView mWeatherLocation; private ImageView mWeatherIcon; - private String mWeatherTemperatureString; + private String mWeatherHighTemperatureString; + private String mWeatherLowTemperatureString; private String mWeatherLocationString; private Drawable mWeatherIconDrawable; private Resources mGenieResources = null; private boolean mDimmed = false; + private boolean mScreenSaverMode = false; private DateFormat mDateFormat; - + private int mBatteryLevel; private boolean mPluggedIn; + private PowerManager.WakeLock mWakeLock; + private int mIdleTimeoutEpoch = 0; + private boolean mWeatherFetchScheduled = false; + private Random mRNG; + + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -145,9 +171,107 @@ public class DeskClock extends Activity { scheduleWeatherFetchDelayed(FETCH_WEATHER_DELAY); } else if (m.what == UPDATE_WEATHER_DISPLAY_MSG) { updateWeatherDisplay(); + } else if (m.what == SCREEN_SAVER_TIMEOUT_MSG) { + if (m.arg1 == mIdleTimeoutEpoch) { + saveScreen(); + } + } else if (m.what == SCREEN_SAVER_MOVE_MSG) { + moveScreenSaver(); } } }; + private void moveScreenSaver() { + moveScreenSaverTo(-1,-1); + } + private void moveScreenSaverTo(int x, int y) { + if (!mScreenSaverMode) return; + + final View time_date = findViewById(R.id.time_date); + + /* + final TranslateAnimation anim = new TranslateAnimation( + Animation.RELATIVE_TO_SELF, 0, // fromX + Animation.RELATIVE_TO_PARENT, 0.5f, // toX + Animation.RELATIVE_TO_SELF, 0, // fromY + Animation.RELATIVE_TO_PARENT, 0.5f // toY + ); + anim.setDuration(1000); + anim.setInterpolator(new android.view.animation.AccelerateDecelerateInterpolator()); + anim.setFillEnabled(true); + anim.setFillAfter(true); + time_date.startAnimation(anim); + */ + + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + + if (x < 0 || y < 0) { + int myWidth = time_date.getMeasuredWidth(); + int myHeight = time_date.getMeasuredHeight(); + x = (int)(mRNG.nextFloat()*(metrics.widthPixels - myWidth)); + y = (int)(mRNG.nextFloat()*(metrics.heightPixels - myHeight)); + } + + time_date.setLayoutParams(new AbsoluteLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + x, + y)); + + mHandy.sendEmptyMessageDelayed(SCREEN_SAVER_MOVE_MSG, SCREEN_SAVER_MOVE_DELAY); + } + + private void restoreScreen() { + if (!mScreenSaverMode) return; + mScreenSaverMode = false; + initViews(); + doDim(false); // restores previous dim mode + refreshAll(); + } + + // Special screen-saver mode for OLED displays that burn in quickly + private void saveScreen() { + if (mScreenSaverMode) return; + + // quickly stash away the x/y of the current date + final View oldTimeDate = findViewById(R.id.time_date); + int oldLoc[] = new int[2]; + oldTimeDate.getLocationOnScreen(oldLoc); + + mScreenSaverMode = true; + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + win.setAttributes(winParams); + + setContentView(R.layout.desk_clock_saver); + + mTime = (DigitalClock) findViewById(R.id.time); + mDate = (TextView) findViewById(R.id.date); + + final int color = mDimmed ? SCREEN_SAVER_COLOR_DIM : SCREEN_SAVER_COLOR; + + ((TextView)findViewById(R.id.timeDisplay)).setTextColor(color); + ((TextView)findViewById(R.id.am_pm)).setTextColor(color); + mDate.setTextColor(color); + + mBatteryDisplay = + mNextAlarm = + mWeatherHighTemperature = + mWeatherLowTemperature = + mWeatherLocation = null; + mWeatherIcon = null; + + refreshDate(); + + moveScreenSaverTo(oldLoc[0], oldLoc[1]); + } + + @Override + public void onUserInteraction() { + if (mScreenSaverMode) + restoreScreen(); + } private boolean supportsWeather() { return (mGenieResources != null); @@ -167,6 +291,16 @@ public class DeskClock extends Activity { mWeatherFetchScheduled = false; } + private static final boolean sCelsius; + static { + String cc = Locale.getDefault().getCountry().toLowerCase(); + sCelsius = !("us".equals(cc) || "bz".equals(cc) || "jm".equals(cc)); + } + + private static int celsiusToLocal(int tempC) { + return sCelsius ? tempC : (int)(tempC * 1.8f + 32); + } + private void fetchWeatherData() { // if we couldn't load the weather widget's resources, we simply // assume it's not present on the device. @@ -197,15 +331,18 @@ public class DeskClock extends Activity { if (cur != null && cur.moveToFirst()) { mWeatherIconDrawable = mGenieResources.getDrawable(cur.getInt( cur.getColumnIndexOrThrow("iconResId"))); - mWeatherTemperatureString = cur.getString( - cur.getColumnIndexOrThrow("highTemperature")); + mWeatherHighTemperatureString = String.format("%d\u00b0", + celsiusToLocal(cur.getInt(cur.getColumnIndexOrThrow("highTemperature")))); + mWeatherLowTemperatureString = String.format("%d\u00b0", + celsiusToLocal(cur.getInt(cur.getColumnIndexOrThrow("lowTemperature")))); mWeatherLocationString = cur.getString( cur.getColumnIndexOrThrow("location")); } else { - Log.w(LOG_TAG, "No weather information available (cur=" + Log.w(LOG_TAG, "No weather information available (cur=" + cur +")"); mWeatherIconDrawable = null; - mWeatherTemperatureString = ""; + mWeatherHighTemperatureString = ""; + mWeatherLowTemperatureString = ""; mWeatherLocationString = "Weather data unavailable."; // TODO: internationalize } @@ -213,12 +350,16 @@ public class DeskClock extends Activity { } private void refreshWeather() { - if (supportsWeather()) + if (supportsWeather()) scheduleWeatherFetchDelayed(0); + updateWeatherDisplay(); // in case we have it cached } private void updateWeatherDisplay() { - mWeatherTemperature.setText(mWeatherTemperatureString); + if (mWeatherHighTemperature == null) return; + + mWeatherHighTemperature.setText(mWeatherHighTemperatureString); + mWeatherLowTemperature.setText(mWeatherLowTemperatureString); mWeatherLocation.setText(mWeatherLocationString); mWeatherIcon.setImageDrawable(mWeatherIconDrawable); } @@ -230,10 +371,18 @@ public class DeskClock extends Activity { mBatteryLevel = batteryLevel; mPluggedIn = pluggedIn; refreshBattery(); + + if (mPluggedIn) { + if (!mWakeLock.isHeld()) mWakeLock.acquire(); + } else { + if (mWakeLock.isHeld()) mWakeLock.release(); + } } } private void refreshBattery() { + if (mBatteryDisplay == null) return; + if (mPluggedIn /* || mBatteryLevel < LOW_BATTERY_THRESHOLD */) { mBatteryDisplay.setCompoundDrawablesWithIntrinsicBounds( 0, 0, android.R.drawable.ic_lock_idle_charging, 0); @@ -250,6 +399,8 @@ public class DeskClock extends Activity { } private void refreshAlarm() { + if (mNextAlarm == null) return; + String nextAlarm = Settings.System.getString(getContentResolver(), Settings.System.NEXT_ALARM_FORMATTED); if (!TextUtils.isEmpty(nextAlarm)) { @@ -271,6 +422,7 @@ public class DeskClock extends Activity { private void doDim(boolean fade) { View tintView = findViewById(R.id.window_tint); + if (tintView == null) return; Window win = getWindow(); WindowManager.LayoutParams winParams = win.getAttributes(); @@ -280,24 +432,22 @@ public class DeskClock extends Activity { if (mDimmed) { winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; -// winParams.flags &= (~WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); winParams.dimAmount = 0.5f; // pump up contrast in dim mode // show the window tint - tintView.startAnimation(AnimationUtils.loadAnimation(this, + tintView.startAnimation(AnimationUtils.loadAnimation(this, fade ? R.anim.dim : R.anim.dim_instant)); } else { winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); -// winParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; winParams.dimAmount = 0.2f; // lower contrast in normal mode - + // hide the window tint - tintView.startAnimation(AnimationUtils.loadAnimation(this, + tintView.startAnimation(AnimationUtils.loadAnimation(this, fade ? R.anim.undim : R.anim.undim_instant)); } - + win.setAttributes(winParams); } @@ -316,6 +466,12 @@ public class DeskClock extends Activity { doDim(false); refreshAll(); + if (mPluggedIn && !mWakeLock.isHeld()) mWakeLock.acquire(); + + mIdleTimeoutEpoch++; + mHandy.sendMessageDelayed( + Message.obtain(mHandy, SCREEN_SAVER_TIMEOUT_MSG, mIdleTimeoutEpoch, 0), + SCREEN_SAVER_TIMEOUT); } @Override @@ -323,6 +479,7 @@ public class DeskClock extends Activity { super.onPause(); unregisterReceiver(mIntentReceiver); unscheduleWeatherFetch(); + if (mWakeLock.isHeld()) mWakeLock.release(); } @@ -333,7 +490,8 @@ public class DeskClock extends Activity { mDate = (TextView) findViewById(R.id.date); mBatteryDisplay = (TextView) findViewById(R.id.battery); - mWeatherTemperature = (TextView) findViewById(R.id.weather_temperature); + mWeatherHighTemperature = (TextView) findViewById(R.id.weather_high_temperature); + mWeatherLowTemperature = (TextView) findViewById(R.id.weather_low_temperature); mWeatherLocation = (TextView) findViewById(R.id.weather_location); mWeatherIcon = (ImageView) findViewById(R.id.weather_icon); @@ -386,20 +544,36 @@ public class DeskClock extends Activity { doDim(true); } }); + + nightmodeButton.setOnLongClickListener(new View.OnLongClickListener() { + public boolean onLongClick(View v) { + saveScreen(); + return true; + } + }); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - initViews(); - doDim(false); - refreshAll(); + if (!mScreenSaverMode) { + initViews(); + doDim(false); + refreshAll(); + } } @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, + "DeskClock"); + mWakeLock.acquire(); + + mRNG = new Random(); + try { mGenieResources = getPackageManager().getResourcesForApplication(GENIE_PACKAGE_ID); } catch (PackageManager.NameNotFoundException e) { -- cgit v1.2.3 From e6cf24dbbea56d8b88a8d48bed9d3a6f26c2ddf0 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Mon, 2 Nov 2009 16:23:52 -0500 Subject: AlarmClock tweaks. - No more layout jumps when switching between dim/undim modes - Now registers itself as the "dock home"---that is, it hijacks the home softkey away from Launcher while the device is docked - Shows "on top" of the keyguard; all exits, however, will return to the keyguard before completing - Now uses the WindowManager to hold a wakelock when necessary - Tweaked weather display (bringing the weather icon size under control) --- src/com/android/deskclock/DeskClock.java | 56 +++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 59963a3a5..c94c362b1 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -79,7 +79,7 @@ import java.util.Random; * DeskClock clock view for desk docks. */ public class DeskClock extends Activity { - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final String LOG_TAG = "DeskClock"; @@ -133,18 +133,15 @@ public class DeskClock extends Activity { private DateFormat mDateFormat; - private int mBatteryLevel; - private boolean mPluggedIn; + private int mBatteryLevel = -1; + private boolean mPluggedIn = false; - private PowerManager.WakeLock mWakeLock; private int mIdleTimeoutEpoch = 0; private boolean mWeatherFetchScheduled = false; private Random mRNG; - - private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -221,6 +218,18 @@ public class DeskClock extends Activity { mHandy.sendEmptyMessageDelayed(SCREEN_SAVER_MOVE_MSG, SCREEN_SAVER_MOVE_DELAY); } + private void setWakeLock(boolean hold) { + if (DEBUG) Log.d(LOG_TAG, (hold ? "hold" : " releas") + "ing wake lock"); + Window win = getWindow(); + WindowManager.LayoutParams winParams = win.getAttributes(); + winParams.flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; + if (hold) + winParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + else + winParams.flags &= (~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + win.setAttributes(winParams); + } + private void restoreScreen() { if (!mScreenSaverMode) return; mScreenSaverMode = false; @@ -244,6 +253,10 @@ public class DeskClock extends Activity { winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; win.setAttributes(winParams); + // give up any internal focus before we switch layouts + final View focused = getCurrentFocus(); + if (focused != null) focused.clearFocus(); + setContentView(R.layout.desk_clock_saver); mTime = (DigitalClock) findViewById(R.id.time); @@ -367,16 +380,13 @@ public class DeskClock extends Activity { // Adapted from KeyguardUpdateMonitor.java private void handleBatteryUpdate(int plugStatus, int batteryLevel) { final boolean pluggedIn = (plugStatus == BATTERY_STATUS_CHARGING || plugStatus == BATTERY_STATUS_FULL); + if (pluggedIn != mPluggedIn) { + setWakeLock(pluggedIn); + } if (pluggedIn != mPluggedIn || batteryLevel != mBatteryLevel) { mBatteryLevel = batteryLevel; mPluggedIn = pluggedIn; refreshBattery(); - - if (mPluggedIn) { - if (!mWakeLock.isHeld()) mWakeLock.acquire(); - } else { - if (mWakeLock.isHeld()) mWakeLock.release(); - } } } @@ -427,6 +437,10 @@ public class DeskClock extends Activity { Window win = getWindow(); WindowManager.LayoutParams winParams = win.getAttributes(); + // secret! + winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + // dim the wallpaper somewhat (how much is determined below) winParams.flags |= (WindowManager.LayoutParams.FLAG_DIM_BEHIND); @@ -454,6 +468,11 @@ public class DeskClock extends Activity { @Override public void onResume() { super.onResume(); + // NB: To avoid situations where the user launches Alarm Clock and is + // surprised to find it in dim mode (because it was last used in dim + // mode, but that last use is long in the past), we always un-dim upon + // bringing the activity to the foregreound. + mDimmed = false; // reload the date format in case the user has changed settings // recently @@ -465,8 +484,9 @@ public class DeskClock extends Activity { registerReceiver(mIntentReceiver, filter); doDim(false); + restoreScreen(); refreshAll(); - if (mPluggedIn && !mWakeLock.isHeld()) mWakeLock.acquire(); + setWakeLock(mPluggedIn); mIdleTimeoutEpoch++; mHandy.sendMessageDelayed( @@ -479,11 +499,14 @@ public class DeskClock extends Activity { super.onPause(); unregisterReceiver(mIntentReceiver); unscheduleWeatherFetch(); - if (mWakeLock.isHeld()) mWakeLock.release(); } private void initViews() { + // give up any internal focus before we switch layouts + final View focused = getCurrentFocus(); + if (focused != null) focused.clearFocus(); + setContentView(R.layout.desk_clock); mTime = (DigitalClock) findViewById(R.id.time); @@ -567,11 +590,6 @@ public class DeskClock extends Activity { protected void onCreate(Bundle icicle) { super.onCreate(icicle); - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, - "DeskClock"); - mWakeLock.acquire(); - mRNG = new Random(); try { -- cgit v1.2.3 From df4657361cc0bd0f4671d5e53f18bd48a7e97245 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 3 Nov 2009 12:52:31 -0500 Subject: Change default sort order to sort by alarm time. Will make the same change to AlarmClock in mr2. Bug: 2228476 --- src/com/android/deskclock/Alarm.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/deskclock/Alarm.java b/src/com/android/deskclock/Alarm.java index 13e4bd7b9..7d8c8a32c 100644 --- a/src/com/android/deskclock/Alarm.java +++ b/src/com/android/deskclock/Alarm.java @@ -126,7 +126,8 @@ public final class Alarm implements Parcelable { /** * The default sort order for this table */ - public static final String DEFAULT_SORT_ORDER = _ID + " ASC"; + public static final String DEFAULT_SORT_ORDER = + HOUR + ", " + MINUTES + " ASC"; // Used when filtering enabled alarms. public static final String WHERE_ENABLED = ENABLED + "=1"; -- cgit v1.2.3 From 0a267d0f1c1e18e0f208b60a81bf2f73457a2c56 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Tue, 3 Nov 2009 15:38:39 -0500 Subject: More tweaks per spec. - Synchronize screensaver jumping to the second - Turn on the display when this app is launched (by docking the device) - Show DeskClock on top of the keyguard - Use Math.round() to compute Fahrenheit (sync up with Genie widget) - Stop moving the screensaver while the display is off. (http://b/2234698) - Minor code cleanups. --- src/com/android/deskclock/DeskClock.java | 84 ++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index c94c362b1..a4bc4ff67 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -83,20 +83,31 @@ public class DeskClock extends Activity { private static final String LOG_TAG = "DeskClock"; - private static final String MUSIC_NOW_PLAYING_ACTIVITY = "com.android.music.PLAYBACK_VIEWER"; + // Intent used to start the music player. + private static final String MUSIC_NOW_PLAYING = "com.android.music.PLAYBACK_VIEWER"; - private final long FETCH_WEATHER_DELAY = 60 * 60 * 1000; // 1 hr - private final long SCREEN_SAVER_TIMEOUT = 5 * 60 * 1000; // 5 min - private final long SCREEN_SAVER_MOVE_DELAY = 5 * 1000; // 15 sec + // Interval between polls of the weather widget. Its refresh period is + // likely to be much longer (~3h), but we want to pick up any changes + // within 5 minutes. + private final long FETCH_WEATHER_DELAY = 5 * 60 * 1000; // 5 min + // Delay before engaging the burn-in protection mode (green-on-black). + private final long SCREEN_SAVER_TIMEOUT = 10 * 60 * 1000; // 10 min + + // Repositioning delay in screen saver. + private final long SCREEN_SAVER_MOVE_DELAY = 60 * 1000; // 1 min + + // Color to use for text & graphics in screen saver mode. + private final int SCREEN_SAVER_COLOR = 0xFF008000; + private final int SCREEN_SAVER_COLOR_DIM = 0xFF003000; + + // Internal message IDs. private final int FETCH_WEATHER_DATA_MSG = 0x1000; private final int UPDATE_WEATHER_DISPLAY_MSG = 0x1001; private final int SCREEN_SAVER_TIMEOUT_MSG = 0x2000; private final int SCREEN_SAVER_MOVE_MSG = 0x2001; - private final int SCREEN_SAVER_COLOR = 0xFF008000; - private final int SCREEN_SAVER_COLOR_DIM = 0xFF003000; - + // Weather widget query information. private static final String GENIE_PACKAGE_ID = "com.google.android.apps.genie.geniewidget"; private static final String WEATHER_CONTENT_AUTHORITY = GENIE_PACKAGE_ID + ".weather"; private static final String WEATHER_CONTENT_PATH = "/weather/current"; @@ -110,6 +121,7 @@ public class DeskClock extends Activity { "description", }; + // State variables follow. private DigitalClock mTime; private TextView mDate; @@ -159,8 +171,6 @@ public class DeskClock extends Activity { private final Handler mHandy = new Handler() { @Override public void handleMessage(Message m) { - if (DEBUG) Log.d(LOG_TAG, "handleMessage: " + m.toString()); - if (m.what == FETCH_WEATHER_DATA_MSG) { if (!mWeatherFetchScheduled) return; mWeatherFetchScheduled = false; @@ -177,6 +187,8 @@ public class DeskClock extends Activity { } } }; + + private void moveScreenSaver() { moveScreenSaverTo(-1,-1); } @@ -209,13 +221,19 @@ public class DeskClock extends Activity { y = (int)(mRNG.nextFloat()*(metrics.heightPixels - myHeight)); } + if (DEBUG) Log.d(LOG_TAG, String.format("screen saver: %d: jumping to (%d,%d)", + System.currentTimeMillis(), x, y)); + time_date.setLayoutParams(new AbsoluteLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, x, y)); - mHandy.sendEmptyMessageDelayed(SCREEN_SAVER_MOVE_MSG, SCREEN_SAVER_MOVE_DELAY); + // Synchronize our jumping so that it happens exactly on the second. + mHandy.sendEmptyMessageDelayed(SCREEN_SAVER_MOVE_MSG, + SCREEN_SAVER_MOVE_DELAY + + (1000 - (System.currentTimeMillis() % 1000))); } private void setWakeLock(boolean hold) { @@ -223,6 +241,8 @@ public class DeskClock extends Activity { Window win = getWindow(); WindowManager.LayoutParams winParams = win.getAttributes(); winParams.flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; + winParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; + winParams.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; if (hold) winParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; else @@ -232,6 +252,7 @@ public class DeskClock extends Activity { private void restoreScreen() { if (!mScreenSaverMode) return; + if (DEBUG) Log.d(LOG_TAG, "restoreScreen"); mScreenSaverMode = false; initViews(); doDim(false); // restores previous dim mode @@ -241,6 +262,7 @@ public class DeskClock extends Activity { // Special screen-saver mode for OLED displays that burn in quickly private void saveScreen() { if (mScreenSaverMode) return; + if (DEBUG) Log.d(LOG_TAG, "saveScreen"); // quickly stash away the x/y of the current date final View oldTimeDate = findViewById(R.id.time_date); @@ -311,7 +333,7 @@ public class DeskClock extends Activity { } private static int celsiusToLocal(int tempC) { - return sCelsius ? tempC : (int)(tempC * 1.8f + 32); + return sCelsius ? tempC : (int) Math.round(tempC * 1.8f + 32); } private void fetchWeatherData() { @@ -342,6 +364,19 @@ public class DeskClock extends Activity { } if (cur != null && cur.moveToFirst()) { + if (DEBUG) { + java.lang.StringBuilder sb = + new java.lang.StringBuilder("Weather query result: {"); + for(int i=0; i0) sb.append(", "); + sb.append(cur.getColumnName(i)) + .append("=") + .append(cur.getString(i)); + } + sb.append("}"); + Log.d(LOG_TAG, sb.toString()); + } + mWeatherIconDrawable = mGenieResources.getDrawable(cur.getInt( cur.getColumnIndexOrThrow("iconResId"))); mWeatherHighTemperatureString = String.format("%d\u00b0", @@ -363,7 +398,7 @@ public class DeskClock extends Activity { } private void refreshWeather() { - if (supportsWeather()) + if (supportsWeather()) scheduleWeatherFetchDelayed(0); updateWeatherDisplay(); // in case we have it cached } @@ -468,11 +503,7 @@ public class DeskClock extends Activity { @Override public void onResume() { super.onResume(); - // NB: To avoid situations where the user launches Alarm Clock and is - // surprised to find it in dim mode (because it was last used in dim - // mode, but that last use is long in the past), we always un-dim upon - // bringing the activity to the foregreound. - mDimmed = false; + if (DEBUG) Log.d(LOG_TAG, "onResume"); // reload the date format in case the user has changed settings // recently @@ -485,7 +516,8 @@ public class DeskClock extends Activity { doDim(false); restoreScreen(); - refreshAll(); + refreshAll(); // will schedule periodic weather fetch + setWakeLock(mPluggedIn); mIdleTimeoutEpoch++; @@ -496,9 +528,21 @@ public class DeskClock extends Activity { @Override public void onPause() { - super.onPause(); + if (DEBUG) Log.d(LOG_TAG, "onPause"); + + // Turn off the screen saver. + restoreScreen(); + + // Avoid situations where the user launches Alarm Clock and is + // surprised to find it in dim mode (because it was last used in dim + // mode, but that last use is long in the past). + mDimmed = false; + + // Other things we don't want to be doing in the background. unregisterReceiver(mIntentReceiver); unscheduleWeatherFetch(); + + super.onPause(); } @@ -544,7 +588,7 @@ public class DeskClock extends Activity { musicButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { - startActivity(new Intent(MUSIC_NOW_PLAYING_ACTIVITY)); + startActivity(new Intent(MUSIC_NOW_PLAYING)); } catch (android.content.ActivityNotFoundException e) { Log.e(LOG_TAG, "Couldn't launch music browser", e); } -- cgit v1.2.3 From 4dc051aa9b76d8dffbd2372740eff2735c654540 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 4 Nov 2009 10:13:26 -0500 Subject: Start Gallery in slideshow mode from DeskClock. --- src/com/android/deskclock/DeskClock.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index a4bc4ff67..175e3eefe 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -577,7 +577,8 @@ public class DeskClock extends Activity { try { startActivity(new Intent( Intent.ACTION_VIEW, - android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI)); + android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI) + .putExtra("slideshow", true)); } catch (android.content.ActivityNotFoundException e) { Log.e(LOG_TAG, "Couldn't launch image browser", e); } -- cgit v1.2.3 From f8952fa79d0cc70e5a802fb2624701fbed0736b8 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 4 Nov 2009 23:17:15 -0500 Subject: Integrate final button artwork for DeskClock. --- src/com/android/deskclock/DeskClock.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 175e3eefe..cf9b337d5 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -61,6 +61,7 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView; import android.widget.Button; import android.widget.CheckBox; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -564,14 +565,14 @@ public class DeskClock extends Activity { mNextAlarm = (TextView) findViewById(R.id.nextAlarm); - final Button alarmButton = (Button) findViewById(R.id.alarm_button); + final ImageButton alarmButton = (ImageButton) findViewById(R.id.alarm_button); alarmButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { startActivity(new Intent(DeskClock.this, AlarmClock.class)); } }); - final Button galleryButton = (Button) findViewById(R.id.gallery_button); + final ImageButton galleryButton = (ImageButton) findViewById(R.id.gallery_button); galleryButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { @@ -585,7 +586,7 @@ public class DeskClock extends Activity { } }); - final Button musicButton = (Button) findViewById(R.id.music_button); + final ImageButton musicButton = (ImageButton) findViewById(R.id.music_button); musicButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { @@ -596,7 +597,7 @@ public class DeskClock extends Activity { } }); - final Button homeButton = (Button) findViewById(R.id.home_button); + final ImageButton homeButton = (ImageButton) findViewById(R.id.home_button); homeButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { startActivity( @@ -605,7 +606,7 @@ public class DeskClock extends Activity { } }); - final Button nightmodeButton = (Button) findViewById(R.id.nightmode_button); + final ImageButton nightmodeButton = (ImageButton) findViewById(R.id.nightmode_button); nightmodeButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mDimmed = ! mDimmed; -- cgit v1.2.3 From 1878e99c5ccd7bd2288707a431e0bb5d39879985 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Thu, 5 Nov 2009 09:37:37 -0500 Subject: Fix trackball focus issues on orientation change. http://b/2232758 --- src/com/android/deskclock/DeskClock.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index cf9b337d5..7c0e63e3e 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -558,6 +558,8 @@ public class DeskClock extends Activity { mDate = (TextView) findViewById(R.id.date); mBatteryDisplay = (TextView) findViewById(R.id.battery); + mTime.getRootView().requestFocus(); + mWeatherHighTemperature = (TextView) findViewById(R.id.weather_high_temperature); mWeatherLowTemperature = (TextView) findViewById(R.id.weather_low_temperature); mWeatherLocation = (TextView) findViewById(R.id.weather_location); -- cgit v1.2.3 From e64281b157842af93b793e8c9de3dd9a71825172 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Thu, 5 Nov 2009 16:19:26 -0500 Subject: Launch the Genie widget when the weather is tapped. Fixes: http://b/2240161 --- src/com/android/deskclock/DeskClock.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 7c0e63e3e..51ebbdba3 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -622,6 +622,18 @@ public class DeskClock extends Activity { return true; } }); + + final View weatherView = findViewById(R.id.weather); + weatherView.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (!supportsWeather()) return; + + Intent genieAppQuery = getPackageManager().getLaunchIntentForPackage(GENIE_PACKAGE_ID); + if (genieAppQuery != null) { + startActivity(genieAppQuery); + } + } + }); } @Override -- cgit v1.2.3 From daa1dd3eb9ebb69817babdb74b125dc63889eb45 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 6 Nov 2009 11:27:27 -0500 Subject: DeskClock UI fixes. - darker shade on the user's background - stop clipping the left-hand side of the time (was moving it over to accommodate Clockopia's generous tracking, but it's causing drawing issues) - AM/PM now in Droid Sans - calmer green in screen-saver mode --- src/com/android/deskclock/DeskClock.java | 22 ++++------------------ src/com/android/deskclock/DigitalClock.java | 6 ++++-- 2 files changed, 8 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 51ebbdba3..8b72ee72b 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -99,8 +99,8 @@ public class DeskClock extends Activity { private final long SCREEN_SAVER_MOVE_DELAY = 60 * 1000; // 1 min // Color to use for text & graphics in screen saver mode. - private final int SCREEN_SAVER_COLOR = 0xFF008000; - private final int SCREEN_SAVER_COLOR_DIM = 0xFF003000; + private final int SCREEN_SAVER_COLOR = 0xFF308030; + private final int SCREEN_SAVER_COLOR_DIM = 0xFF183018; // Internal message IDs. private final int FETCH_WEATHER_DATA_MSG = 0x1000; @@ -198,20 +198,6 @@ public class DeskClock extends Activity { final View time_date = findViewById(R.id.time_date); - /* - final TranslateAnimation anim = new TranslateAnimation( - Animation.RELATIVE_TO_SELF, 0, // fromX - Animation.RELATIVE_TO_PARENT, 0.5f, // toX - Animation.RELATIVE_TO_SELF, 0, // fromY - Animation.RELATIVE_TO_PARENT, 0.5f // toY - ); - anim.setDuration(1000); - anim.setInterpolator(new android.view.animation.AccelerateDecelerateInterpolator()); - anim.setFillEnabled(true); - anim.setFillAfter(true); - time_date.startAnimation(anim); - */ - DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); @@ -482,7 +468,7 @@ public class DeskClock extends Activity { if (mDimmed) { winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; - winParams.dimAmount = 0.5f; // pump up contrast in dim mode + winParams.dimAmount = 0.67f; // pump up contrast in dim mode // show the window tint tintView.startAnimation(AnimationUtils.loadAnimation(this, @@ -490,7 +476,7 @@ public class DeskClock extends Activity { : R.anim.dim_instant)); } else { winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); - winParams.dimAmount = 0.2f; // lower contrast in normal mode + winParams.dimAmount = 0.5f; // lower contrast in normal mode // hide the window tint tintView.startAnimation(AnimationUtils.loadAnimation(this, diff --git a/src/com/android/deskclock/DigitalClock.java b/src/com/android/deskclock/DigitalClock.java index d28cc7de7..383ca7b02 100644 --- a/src/com/android/deskclock/DigitalClock.java +++ b/src/com/android/deskclock/DigitalClock.java @@ -68,7 +68,9 @@ public class DigitalClock extends LinearLayout { AmPm(View parent, Typeface tf) { mAmPm = (TextView) parent.findViewById(R.id.am_pm); - mAmPm.setTypeface(tf); + if (tf != null) { + mAmPm.setTypeface(tf); + } String[] ampm = new DateFormatSymbols().getAmPmStrings(); mAmString = ampm[0]; @@ -111,7 +113,7 @@ public class DigitalClock extends LinearLayout { "fonts/Clockopia.ttf"); mTimeDisplay = (TextView) findViewById(R.id.timeDisplay); mTimeDisplay.setTypeface(tf); - mAmPm = new AmPm(this, tf); + mAmPm = new AmPm(this, null); mCalendar = Calendar.getInstance(); setDateFormat(); -- cgit v1.2.3 From 4e8ad3a40141d05ced3279c6a0296b71ffc5117d Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 6 Nov 2009 14:45:45 -0500 Subject: More UI tweaks: - Add an options menu to the main Clock activity. (The only item is "Alarms" to switch to the Alarm activity; no icon yet.) - Now showing current temperature as well as high/low temps. - Remove year from the displayed date. - Tidy layouts. --- src/com/android/deskclock/DeskClock.java | 52 ++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 8b72ee72b..56eadd0ef 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -45,6 +45,7 @@ import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View.OnClickListener; import android.view.View.OnCreateContextMenuListener; @@ -72,6 +73,7 @@ import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Random; @@ -115,6 +117,7 @@ public class DeskClock extends Activity { private static final String[] WEATHER_CONTENT_COLUMNS = new String[] { "location", "timestamp", + "temperature", "highTemperature", "lowTemperature", "iconUrl", @@ -129,11 +132,13 @@ public class DeskClock extends Activity { private TextView mNextAlarm = null; private TextView mBatteryDisplay; + private TextView mWeatherCurrentTemperature; private TextView mWeatherHighTemperature; private TextView mWeatherLowTemperature; private TextView mWeatherLocation; private ImageView mWeatherIcon; + private String mWeatherCurrentTemperatureString; private String mWeatherHighTemperatureString; private String mWeatherLowTemperatureString; private String mWeatherLocationString; @@ -149,6 +154,8 @@ public class DeskClock extends Activity { private int mBatteryLevel = -1; private boolean mPluggedIn = false; + private boolean mInDock = false; + private int mIdleTimeoutEpoch = 0; private boolean mWeatherFetchScheduled = false; @@ -279,6 +286,7 @@ public class DeskClock extends Activity { mBatteryDisplay = mNextAlarm = + mWeatherCurrentTemperature = mWeatherHighTemperature = mWeatherLowTemperature = mWeatherLocation = null; @@ -366,6 +374,8 @@ public class DeskClock extends Activity { mWeatherIconDrawable = mGenieResources.getDrawable(cur.getInt( cur.getColumnIndexOrThrow("iconResId"))); + mWeatherCurrentTemperatureString = String.format("%d\u00b0", + celsiusToLocal(cur.getInt(cur.getColumnIndexOrThrow("temperature")))); mWeatherHighTemperatureString = String.format("%d\u00b0", celsiusToLocal(cur.getInt(cur.getColumnIndexOrThrow("highTemperature")))); mWeatherLowTemperatureString = String.format("%d\u00b0", @@ -391,8 +401,9 @@ public class DeskClock extends Activity { } private void updateWeatherDisplay() { - if (mWeatherHighTemperature == null) return; + if (mWeatherCurrentTemperature == null) return; + mWeatherCurrentTemperature.setText(mWeatherCurrentTemperatureString); mWeatherHighTemperature.setText(mWeatherHighTemperatureString); mWeatherLowTemperature.setText(mWeatherLowTemperatureString); mWeatherLocation.setText(mWeatherLocationString); @@ -494,7 +505,15 @@ public class DeskClock extends Activity { // reload the date format in case the user has changed settings // recently - mDateFormat = java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL); + final SimpleDateFormat dateFormat = (SimpleDateFormat) + java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL); + // This is a little clumsy; we want to honor the locale's date format + // (rather than simply hardcoding "Weekday, Month Date") but + // DateFormat.FULL includes the year (at least, in enUS). So we lop + // that bit off if it's there; should have no effect on + // locale-specific date strings that look different. + mDateFormat = new SimpleDateFormat(dateFormat.toPattern() + .replace(", yyyy", "")); // no year IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_DATE_CHANGED); @@ -511,6 +530,18 @@ public class DeskClock extends Activity { mHandy.sendMessageDelayed( Message.obtain(mHandy, SCREEN_SAVER_TIMEOUT_MSG, mIdleTimeoutEpoch, 0), SCREEN_SAVER_TIMEOUT); + + final boolean launchedFromDock + = getIntent().hasCategory(Intent.CATEGORY_DESK_DOCK); + + if (supportsWeather() && launchedFromDock && !mInDock) { + if (DEBUG) Log.d(LOG_TAG, "Device now docked; forcing weather to refresh right now"); + sendBroadcast( + new Intent("com.google.android.apps.genie.REFRESH") + .putExtra("requestWeather", true)); + } + + mInDock = launchedFromDock; } @Override @@ -546,6 +577,7 @@ public class DeskClock extends Activity { mTime.getRootView().requestFocus(); + mWeatherCurrentTemperature = (TextView) findViewById(R.id.weather_temperature); mWeatherHighTemperature = (TextView) findViewById(R.id.weather_high_temperature); mWeatherLowTemperature = (TextView) findViewById(R.id.weather_low_temperature); mWeatherLocation = (TextView) findViewById(R.id.weather_location); @@ -632,6 +664,22 @@ public class DeskClock extends Activity { } } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_item_alarms) { + startActivity(new Intent(DeskClock.this, AlarmClock.class)); + return true; + } + return false; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.desk_clock_menu, menu); + return true; + } + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); -- cgit v1.2.3 From c7edd6ea840e25cb04d090b6c29dc96c04d2be40 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Mon, 9 Nov 2009 14:59:05 -0500 Subject: Cancel the snooze if the user sets an alarm for before the snooze fires. If the alarm is enabled, check the snooze time to see if it needs to be cancelled. Remove the preference and notification if cancelled. Return the calculated time from setAlarm to avoid another calculation. Bug: 2139162 --- src/com/android/deskclock/Alarms.java | 40 ++++++++++++++++++++++++++++----- src/com/android/deskclock/SetAlarm.java | 28 ++++++++++++----------- 2 files changed, 49 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/Alarms.java b/src/com/android/deskclock/Alarms.java index b6a22f4f7..ebb337b48 100644 --- a/src/com/android/deskclock/Alarms.java +++ b/src/com/android/deskclock/Alarms.java @@ -17,6 +17,7 @@ package com.android.deskclock; import android.app.AlarmManager; +import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ContentResolver; import android.content.ContentValues; @@ -157,8 +158,9 @@ public class Alarms { * @param vibrate corresponds to the VIBRATE column * @param message corresponds to the MESSAGE column * @param alert corresponds to the ALERT column + * @return Time when the alarm will fire. */ - public static void setAlarm( + public static long setAlarm( Context context, int id, boolean enabled, int hour, int minutes, Alarm.DaysOfWeek daysOfWeek, boolean vibrate, String message, String alert) { @@ -187,7 +189,23 @@ public class Alarms { resolver.update(ContentUris.withAppendedId(Alarm.Columns.CONTENT_URI, id), values, null, null); + long timeInMillis = + calculateAlarm(hour, minutes, daysOfWeek).getTimeInMillis(); + + if (enabled) { + // If this alarm fires before the next snooze, clear the snooze to + // enable this alarm. + SharedPreferences prefs = context.getSharedPreferences( + AlarmClock.PREFERENCES, 0); + long snoozeTime = prefs.getLong(PREF_SNOOZE_TIME, 0); + if (timeInMillis < snoozeTime) { + clearSnoozePreference(context, prefs); + } + } + setNextAlert(context); + + return timeInMillis; } /** @@ -367,10 +385,10 @@ public class Alarms { final long time) { SharedPreferences prefs = context.getSharedPreferences( AlarmClock.PREFERENCES, 0); - SharedPreferences.Editor ed = prefs.edit(); if (id == -1) { - clearSnoozePreference(ed); + clearSnoozePreference(context, prefs); } else { + SharedPreferences.Editor ed = prefs.edit(); ed.putInt(PREF_SNOOZE_ID, id); ed.putLong(PREF_SNOOZE_TIME, time); ed.commit(); @@ -391,13 +409,23 @@ public class Alarms { return; } else if (snoozeId == id) { // This is the same id so clear the shared prefs. - clearSnoozePreference(prefs.edit()); + clearSnoozePreference(context, prefs); } } // Helper to remove the snooze preference. Do not use clear because that - // will erase the clock preferences. - private static void clearSnoozePreference(final SharedPreferences.Editor ed) { + // will erase the clock preferences. Also clear the snooze notification in + // the window shade. + private static void clearSnoozePreference(final Context context, + final SharedPreferences prefs) { + final int alarmId = prefs.getInt(PREF_SNOOZE_ID, -1); + if (alarmId != -1) { + NotificationManager nm = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel(alarmId); + } + + final SharedPreferences.Editor ed = prefs.edit(); ed.remove(PREF_SNOOZE_ID); ed.remove(PREF_SNOOZE_TIME); ed.commit(); diff --git a/src/com/android/deskclock/SetAlarm.java b/src/com/android/deskclock/SetAlarm.java index 1acc1d790..7d84bb493 100644 --- a/src/com/android/deskclock/SetAlarm.java +++ b/src/com/android/deskclock/SetAlarm.java @@ -187,19 +187,19 @@ public class SetAlarm extends PreferenceActivity private void saveAlarm() { final String alert = mAlarmPref.getAlertString(); - Alarms.setAlarm(this, mId, mEnabled, mHour, mMinutes, + long time = Alarms.setAlarm(this, mId, mEnabled, mHour, mMinutes, mRepeatPref.getDaysOfWeek(), mVibratePref.isChecked(), mLabel.getText(), alert); if (mEnabled) { - popAlarmSetToast(this, mHour, mMinutes, - mRepeatPref.getDaysOfWeek()); + popAlarmSetToast(this, time); } } /** * Write alarm out to persistent store and pops toast if alarm - * enabled + * enabled. + * Used only in test code. */ private static void saveAlarm( Context context, int id, boolean enabled, int hour, int minute, @@ -209,11 +209,11 @@ public class SetAlarm extends PreferenceActivity + " " + hour + " " + minute + " vibe " + vibrate); // Fix alert string first - Alarms.setAlarm(context, id, enabled, hour, minute, daysOfWeek, vibrate, - label, alert); + long time = Alarms.setAlarm(context, id, enabled, hour, minute, + daysOfWeek, vibrate, label, alert); if (enabled && popToast) { - popAlarmSetToast(context, hour, minute, daysOfWeek); + popAlarmSetToast(context, time); } } @@ -223,8 +223,13 @@ public class SetAlarm extends PreferenceActivity */ static void popAlarmSetToast(Context context, int hour, int minute, Alarm.DaysOfWeek daysOfWeek) { + popAlarmSetToast(context, + Alarms.calculateAlarm(hour, minute, daysOfWeek) + .getTimeInMillis()); + } - String toastText = formatToast(context, hour, minute, daysOfWeek); + private static void popAlarmSetToast(Context context, long timeInMillis) { + String toastText = formatToast(context, timeInMillis); Toast toast = Toast.makeText(context, toastText, Toast.LENGTH_LONG); ToastMaster.setToast(toast); toast.show(); @@ -234,11 +239,8 @@ public class SetAlarm extends PreferenceActivity * format "Alarm set for 2 days 7 hours and 53 minutes from * now" */ - static String formatToast(Context context, int hour, int minute, - Alarm.DaysOfWeek daysOfWeek) { - long alarm = Alarms.calculateAlarm(hour, minute, - daysOfWeek).getTimeInMillis(); - long delta = alarm - System.currentTimeMillis();; + static String formatToast(Context context, long timeInMillis) { + long delta = timeInMillis - System.currentTimeMillis(); long hours = delta / (1000 * 60 * 60); long minutes = delta / (1000 * 60) % 60; long days = hours / 24; -- cgit v1.2.3 From 2f7c9edfb3f65e5defc7c24bd9348dc76d9e65a5 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Tue, 10 Nov 2009 11:05:42 -0800 Subject: Preserve dim status when pausing. Fixes http://b/2251135 (snoozing an alarm leaves the clock in painfully-bright mode). --- src/com/android/deskclock/DeskClock.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 56eadd0ef..2f4cde985 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -548,14 +548,9 @@ public class DeskClock extends Activity { public void onPause() { if (DEBUG) Log.d(LOG_TAG, "onPause"); - // Turn off the screen saver. + // Turn off the screen saver. (But don't un-dim.) restoreScreen(); - // Avoid situations where the user launches Alarm Clock and is - // surprised to find it in dim mode (because it was last used in dim - // mode, but that last use is long in the past). - mDimmed = false; - // Other things we don't want to be doing in the background. unregisterReceiver(mIntentReceiver); unscheduleWeatherFetch(); @@ -563,6 +558,17 @@ public class DeskClock extends Activity { super.onPause(); } + @Override + public void onStop() { + if (DEBUG) Log.d(LOG_TAG, "onStop"); + + // Avoid situations where the user launches Alarm Clock and is + // surprised to find it in dim mode (because it was last used in dim + // mode, but that last use is long in the past). + mDimmed = false; + + super.onStop(); + } private void initViews() { // give up any internal focus before we switch layouts -- cgit v1.2.3 From ba328cd4a23a13732467ba5533a94ac3d7cbbf4e Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 11 Nov 2009 11:03:10 -0800 Subject: Use an alarm to refresh the date at midnight. Fixes http://b/2251125 (we were detecting user-initiated date changes but not the natural rolling of one day into the next). --- src/com/android/deskclock/DeskClock.java | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 2f4cde985..0cb23c85e 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -17,7 +17,9 @@ package com.android.deskclock; import android.app.Activity; +import android.app.AlarmManager; import android.app.AlertDialog; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -74,6 +76,7 @@ import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.Random; @@ -89,6 +92,8 @@ public class DeskClock extends Activity { // Intent used to start the music player. private static final String MUSIC_NOW_PLAYING = "com.android.music.PLAYBACK_VIEWER"; + private static final String ACTION_MIDNIGHT = "com.android.deskclock.MIDNIGHT"; + // Interval between polls of the weather widget. Its refresh period is // likely to be much longer (~3h), but we want to pick up any changes // within 5 minutes. @@ -162,6 +167,8 @@ public class DeskClock extends Activity { private Random mRNG; + private PendingIntent mMidnightIntent; + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -438,7 +445,9 @@ public class DeskClock extends Activity { } private void refreshDate() { - mDate.setText(mDateFormat.format(new Date())); + final Date now = new Date(); + if (DEBUG) Log.d(LOG_TAG, "refreshing date..." + now); + mDate.setText(mDateFormat.format(now)); } private void refreshAlarm() { @@ -470,7 +479,6 @@ public class DeskClock extends Activity { Window win = getWindow(); WindowManager.LayoutParams winParams = win.getAttributes(); - // secret! winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); @@ -518,6 +526,13 @@ public class DeskClock extends Activity { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_DATE_CHANGED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(ACTION_MIDNIGHT); + + Calendar today = Calendar.getInstance(); + today.add(Calendar.DATE, 1); + mMidnightIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_MIDNIGHT), 0); + AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + am.setRepeating(AlarmManager.RTC, today.getTimeInMillis(), AlarmManager.INTERVAL_DAY, mMidnightIntent); registerReceiver(mIntentReceiver, filter); doDim(false); @@ -553,6 +568,8 @@ public class DeskClock extends Activity { // Other things we don't want to be doing in the background. unregisterReceiver(mIntentReceiver); + AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + am.cancel(mMidnightIntent); unscheduleWeatherFetch(); super.onPause(); -- cgit v1.2.3 From 9463551d754302e158b80357d9c5c88517fe6e58 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 11 Nov 2009 11:27:00 -0800 Subject: Fix http://b/2232909 . Rather than being fancy and firing off the NOW_PLAYING activity (which doesn't like to be launched if nothing's playing), we just launch the Music app as if it were starting from Launcher. --- src/com/android/deskclock/DeskClock.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 0cb23c85e..c7164fb25 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -89,9 +89,10 @@ public class DeskClock extends Activity { private static final String LOG_TAG = "DeskClock"; - // Intent used to start the music player. - private static final String MUSIC_NOW_PLAYING = "com.android.music.PLAYBACK_VIEWER"; + // Package ID of the music player. + private static final String MUSIC_PACKAGE_ID = "com.android.music"; + // Alarm action for midnight (so we can update the date display). private static final String ACTION_MIDNIGHT = "com.android.deskclock.MIDNIGHT"; // Interval between polls of the weather widget. Its refresh period is @@ -633,7 +634,10 @@ public class DeskClock extends Activity { musicButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { - startActivity(new Intent(MUSIC_NOW_PLAYING)); + Intent musicAppQuery = getPackageManager().getLaunchIntentForPackage(MUSIC_PACKAGE_ID); + if (musicAppQuery != null) { + startActivity(musicAppQuery); + } } catch (android.content.ActivityNotFoundException e) { Log.e(LOG_TAG, "Couldn't launch music browser", e); } -- cgit v1.2.3 From 0c4449d6fd421c916ebaf9657ff6ae5a21eed2e7 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 11 Nov 2009 23:09:28 -0800 Subject: Fix http://b/2235284 by putting the next alarm in the Clock screen saver mode. TBR. --- src/com/android/deskclock/DeskClock.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index c7164fb25..f8b71291a 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -211,14 +211,14 @@ public class DeskClock extends Activity { private void moveScreenSaverTo(int x, int y) { if (!mScreenSaverMode) return; - final View time_date = findViewById(R.id.time_date); + final View saver_view = findViewById(R.id.saver_view); DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); if (x < 0 || y < 0) { - int myWidth = time_date.getMeasuredWidth(); - int myHeight = time_date.getMeasuredHeight(); + int myWidth = saver_view.getMeasuredWidth(); + int myHeight = saver_view.getMeasuredHeight(); x = (int)(mRNG.nextFloat()*(metrics.widthPixels - myWidth)); y = (int)(mRNG.nextFloat()*(metrics.heightPixels - myHeight)); } @@ -226,7 +226,7 @@ public class DeskClock extends Activity { if (DEBUG) Log.d(LOG_TAG, String.format("screen saver: %d: jumping to (%d,%d)", System.currentTimeMillis(), x, y)); - time_date.setLayoutParams(new AbsoluteLayout.LayoutParams( + saver_view.setLayoutParams(new AbsoluteLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, x, @@ -285,15 +285,21 @@ public class DeskClock extends Activity { mTime = (DigitalClock) findViewById(R.id.time); mDate = (TextView) findViewById(R.id.date); + mNextAlarm = (TextView) findViewById(R.id.nextAlarm); final int color = mDimmed ? SCREEN_SAVER_COLOR_DIM : SCREEN_SAVER_COLOR; ((TextView)findViewById(R.id.timeDisplay)).setTextColor(color); ((TextView)findViewById(R.id.am_pm)).setTextColor(color); mDate.setTextColor(color); + mNextAlarm.setTextColor(color); + mNextAlarm.setCompoundDrawablesWithIntrinsicBounds( + getResources().getDrawable(mDimmed + ? R.drawable.ic_lock_idle_alarm_saver_dim + : R.drawable.ic_lock_idle_alarm_saver), + null, null, null); mBatteryDisplay = - mNextAlarm = mWeatherCurrentTemperature = mWeatherHighTemperature = mWeatherLowTemperature = @@ -301,6 +307,7 @@ public class DeskClock extends Activity { mWeatherIcon = null; refreshDate(); + refreshAlarm(); moveScreenSaverTo(oldLoc[0], oldLoc[1]); } -- cgit v1.2.3 From 8423a1772b340c02e07c066b646baeae18521fe7 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Thu, 12 Nov 2009 13:40:19 -0800 Subject: Fix http://b/2249983 . Place the other activities in Clock in their own task affinity and hide them from the recent apps list. Additionally, launch other apps with the new task flag (since we're behaving like a launcher). --- src/com/android/deskclock/DeskClock.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index f8b71291a..e7060b5a3 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -630,7 +630,8 @@ public class DeskClock extends Activity { startActivity(new Intent( Intent.ACTION_VIEW, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI) - .putExtra("slideshow", true)); + .putExtra("slideshow", true) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } catch (android.content.ActivityNotFoundException e) { Log.e(LOG_TAG, "Couldn't launch image browser", e); } @@ -641,7 +642,9 @@ public class DeskClock extends Activity { musicButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { - Intent musicAppQuery = getPackageManager().getLaunchIntentForPackage(MUSIC_PACKAGE_ID); + Intent musicAppQuery = getPackageManager() + .getLaunchIntentForPackage(MUSIC_PACKAGE_ID) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (musicAppQuery != null) { startActivity(musicAppQuery); } @@ -656,6 +659,7 @@ public class DeskClock extends Activity { public void onClick(View v) { startActivity( new Intent(Intent.ACTION_MAIN) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addCategory(Intent.CATEGORY_HOME)); } }); @@ -680,7 +684,9 @@ public class DeskClock extends Activity { public void onClick(View v) { if (!supportsWeather()) return; - Intent genieAppQuery = getPackageManager().getLaunchIntentForPackage(GENIE_PACKAGE_ID); + Intent genieAppQuery = getPackageManager() + .getLaunchIntentForPackage(GENIE_PACKAGE_ID) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (genieAppQuery != null) { startActivity(genieAppQuery); } -- cgit v1.2.3 From 2ff01ef636c8896455e6780fbe14cd0dca207969 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 13 Nov 2009 12:02:08 -0800 Subject: Fine-tuning the DeskClock UI. Date format used is the new full_wday_month_day_no_year, which should fix http://b/2254472 and http://b/2247356 . Font sizes and colors tweaked to match latest spec. --- src/com/android/deskclock/DeskClock.java | 33 +++++++------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index e7060b5a3..654d6d93a 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -41,6 +41,7 @@ import android.os.SystemClock; import android.os.PowerManager; import android.provider.Settings; import android.text.TextUtils; +import android.text.format.DateFormat; import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextMenu.ContextMenuInfo; @@ -74,8 +75,6 @@ import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; import java.io.IOException; import java.io.InputStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; @@ -155,7 +154,7 @@ public class DeskClock extends Activity { private boolean mDimmed = false; private boolean mScreenSaverMode = false; - private DateFormat mDateFormat; + private String mDateFormat; private int mBatteryLevel = -1; private boolean mPluggedIn = false; @@ -336,16 +335,6 @@ public class DeskClock extends Activity { mWeatherFetchScheduled = false; } - private static final boolean sCelsius; - static { - String cc = Locale.getDefault().getCountry().toLowerCase(); - sCelsius = !("us".equals(cc) || "bz".equals(cc) || "jm".equals(cc)); - } - - private static int celsiusToLocal(int tempC) { - return sCelsius ? tempC : (int) Math.round(tempC * 1.8f + 32); - } - private void fetchWeatherData() { // if we couldn't load the weather widget's resources, we simply // assume it's not present on the device. @@ -390,11 +379,11 @@ public class DeskClock extends Activity { mWeatherIconDrawable = mGenieResources.getDrawable(cur.getInt( cur.getColumnIndexOrThrow("iconResId"))); mWeatherCurrentTemperatureString = String.format("%d\u00b0", - celsiusToLocal(cur.getInt(cur.getColumnIndexOrThrow("temperature")))); + (cur.getInt(cur.getColumnIndexOrThrow("temperature")))); mWeatherHighTemperatureString = String.format("%d\u00b0", - celsiusToLocal(cur.getInt(cur.getColumnIndexOrThrow("highTemperature")))); + (cur.getInt(cur.getColumnIndexOrThrow("highTemperature")))); mWeatherLowTemperatureString = String.format("%d\u00b0", - celsiusToLocal(cur.getInt(cur.getColumnIndexOrThrow("lowTemperature")))); + (cur.getInt(cur.getColumnIndexOrThrow("lowTemperature")))); mWeatherLocationString = cur.getString( cur.getColumnIndexOrThrow("location")); } else { @@ -455,7 +444,7 @@ public class DeskClock extends Activity { private void refreshDate() { final Date now = new Date(); if (DEBUG) Log.d(LOG_TAG, "refreshing date..." + now); - mDate.setText(mDateFormat.format(now)); + mDate.setText(DateFormat.format(mDateFormat, now)); } private void refreshAlarm() { @@ -521,15 +510,7 @@ public class DeskClock extends Activity { // reload the date format in case the user has changed settings // recently - final SimpleDateFormat dateFormat = (SimpleDateFormat) - java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL); - // This is a little clumsy; we want to honor the locale's date format - // (rather than simply hardcoding "Weekday, Month Date") but - // DateFormat.FULL includes the year (at least, in enUS). So we lop - // that bit off if it's there; should have no effect on - // locale-specific date strings that look different. - mDateFormat = new SimpleDateFormat(dateFormat.toPattern() - .replace(", yyyy", "")); // no year + mDateFormat = getString(com.android.internal.R.string.full_wday_month_day_no_year); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_DATE_CHANGED); -- cgit v1.2.3 From 4e1b06ebcf08dd57240a56bac6f923b2e72ea793 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Mon, 16 Nov 2009 07:50:20 -0500 Subject: New UI for the list of alarms. Still need to put the "Add alarm" item at the top and the clock at the bottom. --- src/com/android/deskclock/AlarmClock.java | 50 +++++++++++++++++++++++------ src/com/android/deskclock/DigitalClock.java | 11 ++++--- 2 files changed, 46 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java index e9d5e7866..f2533c329 100644 --- a/src/com/android/deskclock/AlarmClock.java +++ b/src/com/android/deskclock/AlarmClock.java @@ -25,6 +25,7 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; import android.database.DataSetObserver; +import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -44,6 +45,7 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.CursorAdapter; +import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; @@ -68,6 +70,17 @@ public class AlarmClock extends Activity implements OnItemClickListener { private ListView mAlarmsList; private Cursor mCursor; + private void updateIndicatorAndAlarm(boolean enabled, ImageView bar, + Alarm alarm) { + bar.setImageResource(enabled ? R.drawable.ic_indicator_on + : R.drawable.ic_indicator_off); + Alarms.enableAlarm(this, alarm.id, enabled); + if (enabled) { + SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes, + alarm.daysOfWeek); + } + } + private class AlarmTimeAdapter extends CursorAdapter { public AlarmTimeAdapter(Context context, Cursor cursor) { super(context, cursor); @@ -85,17 +98,33 @@ public class AlarmClock extends Activity implements OnItemClickListener { public void bindView(View view, Context context, Cursor cursor) { final Alarm alarm = new Alarm(cursor); - CheckBox onButton = (CheckBox)view.findViewById(R.id.alarmButton); - onButton.setChecked(alarm.enabled); - onButton.setOnClickListener(new OnClickListener() { + View indicator = view.findViewById(R.id.indicator); + + // Set the initial resource for the bar image. + final ImageView barOnOff = + (ImageView) indicator.findViewById(R.id.bar_onoff); + barOnOff.setImageResource(alarm.enabled ? + R.drawable.ic_indicator_on : R.drawable.ic_indicator_off); + + // Set the initial state of the clock "checkbox" + final CheckBox clockOnOff = + (CheckBox) indicator.findViewById(R.id.clock_onoff); + clockOnOff.setChecked(alarm.enabled); + + // Handle the "checkbox" click and toggle the alarm. + clockOnOff.setOnClickListener(new OnClickListener() { public void onClick(View v) { boolean isChecked = ((CheckBox) v).isChecked(); - Alarms.enableAlarm(AlarmClock.this, alarm.id, - isChecked); - if (isChecked) { - SetAlarm.popAlarmSetToast(AlarmClock.this, - alarm.hour, alarm.minutes, alarm.daysOfWeek); - } + updateIndicatorAndAlarm(isChecked, barOnOff, alarm); + } + }); + + // Clicking outside the "checkbox" should also change the state. + indicator.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + clockOnOff.toggle(); + updateIndicatorAndAlarm(clockOnOff.isChecked(), + barOnOff, alarm); } }); @@ -107,6 +136,7 @@ public class AlarmClock extends Activity implements OnItemClickListener { c.set(Calendar.HOUR_OF_DAY, alarm.hour); c.set(Calendar.MINUTE, alarm.minutes); digitalClock.updateTime(c); + digitalClock.setTypeface(Typeface.DEFAULT); // Set the repeat text or leave it blank if it does not repeat. TextView daysOfWeekView = @@ -122,7 +152,7 @@ public class AlarmClock extends Activity implements OnItemClickListener { // Display the label TextView labelView = - (TextView) digitalClock.findViewById(R.id.label); + (TextView) view.findViewById(R.id.label); if (alarm.label != null && alarm.label.length() != 0) { labelView.setText(alarm.label); labelView.setVisibility(View.VISIBLE); diff --git a/src/com/android/deskclock/DigitalClock.java b/src/com/android/deskclock/DigitalClock.java index 383ca7b02..38ed91b5d 100644 --- a/src/com/android/deskclock/DigitalClock.java +++ b/src/com/android/deskclock/DigitalClock.java @@ -66,11 +66,8 @@ public class DigitalClock extends LinearLayout { private TextView mAmPm; private String mAmString, mPmString; - AmPm(View parent, Typeface tf) { + AmPm(View parent) { mAmPm = (TextView) parent.findViewById(R.id.am_pm); - if (tf != null) { - mAmPm.setTypeface(tf); - } String[] ampm = new DateFormatSymbols().getAmPmStrings(); mAmString = ampm[0]; @@ -113,7 +110,7 @@ public class DigitalClock extends LinearLayout { "fonts/Clockopia.ttf"); mTimeDisplay = (TextView) findViewById(R.id.timeDisplay); mTimeDisplay.setTypeface(tf); - mAmPm = new AmPm(this, null); + mAmPm = new AmPm(this); mCalendar = Calendar.getInstance(); setDateFormat(); @@ -183,4 +180,8 @@ public class DigitalClock extends LinearLayout { void setLive(boolean live) { mLive = live; } + + void setTypeface(Typeface tf) { + mTimeDisplay.setTypeface(tf); + } } -- cgit v1.2.3 From c31f08c3c16b83fb5263e87361be8f04abdd8b90 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Mon, 16 Nov 2009 11:56:04 -0500 Subject: Make the default ringtone selected in the list. Bug: 2260622 --- src/com/android/deskclock/AlarmPreference.java | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmPreference.java b/src/com/android/deskclock/AlarmPreference.java index c1b94f85d..a977728d1 100644 --- a/src/com/android/deskclock/AlarmPreference.java +++ b/src/com/android/deskclock/AlarmPreference.java @@ -41,6 +41,10 @@ public class AlarmPreference extends RingtonePreference { @Override protected Uri onRestoreRingtone() { + if (RingtoneManager.isDefault(mAlert)) { + return RingtoneManager.getActualDefaultRingtoneUri(getContext(), + RingtoneManager.TYPE_ALARM); + } return mAlert; } -- cgit v1.2.3 From e3f01aff06d4bddb09215ccad3d8502de9164966 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Mon, 16 Nov 2009 16:11:32 -0500 Subject: Finish the UI improvements for the alarm list. --- src/com/android/deskclock/AlarmClock.java | 46 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java index f2533c329..acf42b3db 100644 --- a/src/com/android/deskclock/AlarmClock.java +++ b/src/com/android/deskclock/AlarmClock.java @@ -24,7 +24,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; -import android.database.DataSetObserver; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; @@ -45,6 +44,7 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.CursorAdapter; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; @@ -58,9 +58,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { final static String PREFERENCES = "AlarmClock"; - /** Cap alarm count at this number */ - final static int MAX_ALARM_COUNT = 12; - /** This must be false for production. If true, turns on logging, test code, etc. */ final static boolean DEBUG = false; @@ -221,7 +218,7 @@ public class AlarmClock extends Activity implements OnItemClickListener { mAlarmsList.setOnItemClickListener(this); mAlarmsList.setOnCreateContextMenuListener(this); - final Button addAlarm = (Button) findViewById(R.id.add_alarm); + View addAlarm = findViewById(R.id.add_alarm); addAlarm.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Uri uri = Alarms.addAlarm(getContentResolver()); @@ -237,28 +234,14 @@ public class AlarmClock extends Activity implements OnItemClickListener { startActivity(intent); } }); - // Update the enabled state of the "Add alarm" menu item depending on - // how many alarms are set. - adapter.registerDataSetObserver(new DataSetObserver() { - public void onChanged() { - updateAddAlarmButton(addAlarm); - } - public void onInvalidate() { - updateAddAlarmButton(addAlarm); - } - }); - Button settings = (Button) findViewById(R.id.settings); - settings.setOnClickListener(new View.OnClickListener() { + ImageButton deskClock = + (ImageButton) findViewById(R.id.desk_clock_button); + deskClock.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - startActivity( - new Intent(AlarmClock.this, SettingsActivity.class)); + startActivity(new Intent(AlarmClock.this, DeskClock.class)); } - }); - } - - private void updateAddAlarmButton(Button b) { - b.setEnabled(mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); + }); } @Override @@ -301,6 +284,21 @@ public class AlarmClock extends Activity implements OnItemClickListener { } } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_item_settings) { + startActivity(new Intent(this, SettingsActivity.class)); + return true; + } + return false; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.alarm_list_menu, menu); + return true; + } + public void onItemClick(AdapterView parent, View v, int pos, long id) { Intent intent = new Intent(this, SetAlarm.class); intent.putExtra(Alarms.ALARM_ID, (int) id); -- cgit v1.2.3 From f1e8069bcd1755d9ef2322b705d8d9d6b83728f0 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Wed, 18 Nov 2009 09:53:03 -0500 Subject: UI tweaks to meet Jeff's spec. Colors and text sizes are exactly how Jeff specified in the pdf. I tried to match the size of everything based on looks but I might be a little off in padding. The shadow effect on the bottom clock has been left out for now since there are no specifics in the spec for each of the shadow components and I cannot figure it out based on the drawing. --- src/com/android/deskclock/AlarmClock.java | 53 +++++++++++++++++++------------ 1 file changed, 33 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java index acf42b3db..e872eadfd 100644 --- a/src/com/android/deskclock/AlarmClock.java +++ b/src/com/android/deskclock/AlarmClock.java @@ -192,6 +192,12 @@ public class AlarmClock extends Activity implements OnItemClickListener { } return true; + case R.id.edit_alarm: + Intent intent = new Intent(this, SetAlarm.class); + intent.putExtra(Alarms.ALARM_ID, id); + startActivity(intent); + return true; + default: break; } @@ -221,17 +227,7 @@ public class AlarmClock extends Activity implements OnItemClickListener { View addAlarm = findViewById(R.id.add_alarm); addAlarm.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - Uri uri = Alarms.addAlarm(getContentResolver()); - // FIXME: scroll to new item? - String segment = uri.getPathSegments().get(1); - int newId = Integer.parseInt(segment); - if (Log.LOGV) { - Log.v("In AlarmClock, new alarm id = " + newId); - } - Intent intent = - new Intent(AlarmClock.this, SetAlarm.class); - intent.putExtra(Alarms.ALARM_ID, newId); - startActivity(intent); + addNewAlarm(); } }); @@ -244,6 +240,18 @@ public class AlarmClock extends Activity implements OnItemClickListener { }); } + private void addNewAlarm() { + Uri uri = Alarms.addAlarm(getContentResolver()); + String segment = uri.getPathSegments().get(1); + int newId = Integer.parseInt(segment); + if (Log.LOGV) { + Log.v("In AlarmClock, new alarm id = " + newId); + } + Intent intent = new Intent(this, SetAlarm.class); + intent.putExtra(Alarms.ALARM_ID, newId); + startActivity(intent); + } + @Override protected void onDestroy() { super.onDestroy(); @@ -278,25 +286,30 @@ public class AlarmClock extends Activity implements OnItemClickListener { // Set the custom view on the menu. menu.setHeaderView(v); - // Change the text to "disable" if the alarm is already enabled. - if (alarm.enabled) { - menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm); - } } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.menu_item_settings) { - startActivity(new Intent(this, SettingsActivity.class)); - return true; + switch (item.getItemId()) { + case R.id.menu_item_settings: + startActivity(new Intent(this, SettingsActivity.class)); + return true; + case R.id.menu_item_desk_clock: + startActivity(new Intent(this, DeskClock.class)); + return true; + case R.id.menu_item_add_alarm: + addNewAlarm(); + return true; + default: + break; } - return false; + return super.onOptionsItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.alarm_list_menu, menu); - return true; + return super.onCreateOptionsMenu(menu); } public void onItemClick(AdapterView parent, View v, int pos, long id) { -- cgit v1.2.3 From b95b7a63c403a17c3725e1ccfbd65b4283fd6a0f Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 18 Nov 2009 14:21:01 -0500 Subject: Myriad fixes & cleanups in DeskClock. - Force the weather widget to actually fetch new data at the moment the device is docked, or when the screensaver disappears (if the device is plugged in). - Use Intent.FLAG_ACTIVITY_CLEAR_TOP to launch weather, music, and gallery to avoid jumping into the middle of their task stacks (http://b/2267831) - Fix layout issues with weather, particularly when the Genie widget is unavailable. (relates to http://b/2254472) - Internationalize the error message shown when Genie malfunctions. - Clean up weather & time displays to match UX specs more closely (fonts, shadow crop issues). - Remove some tabs in XML. - Internal cleanup: use "query" in the name of functions & variables related to getting updated weather from Genie; use "fetch" to refer to downloading new weather data from the network --- src/com/android/deskclock/DeskClock.java | 96 +++++++++++++++++++------------- 1 file changed, 57 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 654d6d93a..0cd65a574 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -97,7 +97,7 @@ public class DeskClock extends Activity { // Interval between polls of the weather widget. Its refresh period is // likely to be much longer (~3h), but we want to pick up any changes // within 5 minutes. - private final long FETCH_WEATHER_DELAY = 5 * 60 * 1000; // 5 min + private final long QUERY_WEATHER_DELAY = 5 * 60 * 1000; // 5 min // Delay before engaging the burn-in protection mode (green-on-black). private final long SCREEN_SAVER_TIMEOUT = 10 * 60 * 1000; // 10 min @@ -109,8 +109,12 @@ public class DeskClock extends Activity { private final int SCREEN_SAVER_COLOR = 0xFF308030; private final int SCREEN_SAVER_COLOR_DIM = 0xFF183018; + // Opacity of black layer between clock display and wallpaper. + private final float DIM_BEHIND_AMOUNT_NORMAL = 0.4f; + private final float DIM_BEHIND_AMOUNT_DIMMED = 0.7f; // higher contrast when display dimmed + // Internal message IDs. - private final int FETCH_WEATHER_DATA_MSG = 0x1000; + private final int QUERY_WEATHER_DATA_MSG = 0x1000; private final int UPDATE_WEATHER_DISPLAY_MSG = 0x1001; private final int SCREEN_SAVER_TIMEOUT_MSG = 0x2000; private final int SCREEN_SAVER_MOVE_MSG = 0x2001; @@ -130,6 +134,8 @@ public class DeskClock extends Activity { "description", }; + private static final String ACTION_GENIE_REFRESH = "com.google.android.apps.genie.REFRESH"; + // State variables follow. private DigitalClock mTime; private TextView mDate; @@ -163,8 +169,6 @@ public class DeskClock extends Activity { private int mIdleTimeoutEpoch = 0; - private boolean mWeatherFetchScheduled = false; - private Random mRNG; private PendingIntent mMidnightIntent; @@ -186,11 +190,9 @@ public class DeskClock extends Activity { private final Handler mHandy = new Handler() { @Override public void handleMessage(Message m) { - if (m.what == FETCH_WEATHER_DATA_MSG) { - if (!mWeatherFetchScheduled) return; - mWeatherFetchScheduled = false; - new Thread() { public void run() { fetchWeatherData(); } }.start(); - scheduleWeatherFetchDelayed(FETCH_WEATHER_DELAY); + if (m.what == QUERY_WEATHER_DATA_MSG) { + new Thread() { public void run() { queryWeatherData(); } }.start(); + scheduleWeatherQueryDelayed(QUERY_WEATHER_DELAY); } else if (m.what == UPDATE_WEATHER_DISPLAY_MSG) { updateWeatherDisplay(); } else if (m.what == SCREEN_SAVER_TIMEOUT_MSG) { @@ -257,6 +259,8 @@ public class DeskClock extends Activity { mScreenSaverMode = false; initViews(); doDim(false); // restores previous dim mode + // policy: update weather info when returning from screen saver + if (mPluggedIn) requestWeatherDataFetch(); refreshAll(); } @@ -293,8 +297,8 @@ public class DeskClock extends Activity { mDate.setTextColor(color); mNextAlarm.setTextColor(color); mNextAlarm.setCompoundDrawablesWithIntrinsicBounds( - getResources().getDrawable(mDimmed - ? R.drawable.ic_lock_idle_alarm_saver_dim + getResources().getDrawable(mDimmed + ? R.drawable.ic_lock_idle_alarm_saver_dim : R.drawable.ic_lock_idle_alarm_saver), null, null, null); @@ -317,25 +321,32 @@ public class DeskClock extends Activity { restoreScreen(); } + // Tell the Genie widget to load new data from the network. + private void requestWeatherDataFetch() { + if (DEBUG) Log.d(LOG_TAG, "forcing the Genie widget to update weather now..."); + sendBroadcast(new Intent(ACTION_GENIE_REFRESH).putExtra("requestWeather", true)); + // update the display with any new data + scheduleWeatherQueryDelayed(5000); + } + private boolean supportsWeather() { return (mGenieResources != null); } - private void scheduleWeatherFetchDelayed(long delay) { - if (mWeatherFetchScheduled) return; + private void scheduleWeatherQueryDelayed(long delay) { + // cancel any existing scheduled queries + unscheduleWeatherQuery(); if (DEBUG) Log.d(LOG_TAG, "scheduling weather fetch message for " + delay + "ms from now"); - mWeatherFetchScheduled = true; - - mHandy.sendEmptyMessageDelayed(FETCH_WEATHER_DATA_MSG, delay); + mHandy.sendEmptyMessageDelayed(QUERY_WEATHER_DATA_MSG, delay); } - private void unscheduleWeatherFetch() { - mWeatherFetchScheduled = false; + private void unscheduleWeatherQuery() { + mHandy.removeMessages(QUERY_WEATHER_DATA_MSG); } - private void fetchWeatherData() { + private void queryWeatherData() { // if we couldn't load the weather widget's resources, we simply // assume it's not present on the device. if (mGenieResources == null) return; @@ -392,7 +403,7 @@ public class DeskClock extends Activity { mWeatherIconDrawable = null; mWeatherHighTemperatureString = ""; mWeatherLowTemperatureString = ""; - mWeatherLocationString = "Weather data unavailable."; // TODO: internationalize + mWeatherLocationString = getString(R.string.weather_fetch_failure); } mHandy.sendEmptyMessage(UPDATE_WEATHER_DISPLAY_MSG); @@ -400,7 +411,7 @@ public class DeskClock extends Activity { private void refreshWeather() { if (supportsWeather()) - scheduleWeatherFetchDelayed(0); + scheduleWeatherQueryDelayed(0); updateWeatherDisplay(); // in case we have it cached } @@ -419,6 +430,11 @@ public class DeskClock extends Activity { final boolean pluggedIn = (plugStatus == BATTERY_STATUS_CHARGING || plugStatus == BATTERY_STATUS_FULL); if (pluggedIn != mPluggedIn) { setWakeLock(pluggedIn); + + if (pluggedIn) { + // policy: update weather info when attaching to power + requestWeatherDataFetch(); + } } if (pluggedIn != mPluggedIn || batteryLevel != mBatteryLevel) { mBatteryLevel = batteryLevel; @@ -484,7 +500,7 @@ public class DeskClock extends Activity { if (mDimmed) { winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; - winParams.dimAmount = 0.67f; // pump up contrast in dim mode + winParams.dimAmount = DIM_BEHIND_AMOUNT_DIMMED; // show the window tint tintView.startAnimation(AnimationUtils.loadAnimation(this, @@ -492,7 +508,7 @@ public class DeskClock extends Activity { : R.anim.dim_instant)); } else { winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); - winParams.dimAmount = 0.5f; // lower contrast in normal mode + winParams.dimAmount = DIM_BEHIND_AMOUNT_NORMAL; // hide the window tint tintView.startAnimation(AnimationUtils.loadAnimation(this, @@ -524,8 +540,8 @@ public class DeskClock extends Activity { am.setRepeating(AlarmManager.RTC, today.getTimeInMillis(), AlarmManager.INTERVAL_DAY, mMidnightIntent); registerReceiver(mIntentReceiver, filter); - doDim(false); - restoreScreen(); + doDim(false); // un-dim when resuming + restoreScreen(); // disable screen saver refreshAll(); // will schedule periodic weather fetch setWakeLock(mPluggedIn); @@ -535,14 +551,13 @@ public class DeskClock extends Activity { Message.obtain(mHandy, SCREEN_SAVER_TIMEOUT_MSG, mIdleTimeoutEpoch, 0), SCREEN_SAVER_TIMEOUT); - final boolean launchedFromDock + final boolean launchedFromDock = getIntent().hasCategory(Intent.CATEGORY_DESK_DOCK); if (supportsWeather() && launchedFromDock && !mInDock) { + // policy: fetch weather if launched via dock connection if (DEBUG) Log.d(LOG_TAG, "Device now docked; forcing weather to refresh right now"); - sendBroadcast( - new Intent("com.google.android.apps.genie.REFRESH") - .putExtra("requestWeather", true)); + requestWeatherDataFetch(); } mInDock = launchedFromDock; @@ -559,7 +574,7 @@ public class DeskClock extends Activity { unregisterReceiver(mIntentReceiver); AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); am.cancel(mMidnightIntent); - unscheduleWeatherFetch(); + unscheduleWeatherQuery(); super.onPause(); } @@ -595,14 +610,17 @@ public class DeskClock extends Activity { mWeatherLocation = (TextView) findViewById(R.id.weather_location); mWeatherIcon = (ImageView) findViewById(R.id.weather_icon); - mNextAlarm = (TextView) findViewById(R.id.nextAlarm); - - final ImageButton alarmButton = (ImageButton) findViewById(R.id.alarm_button); - alarmButton.setOnClickListener(new View.OnClickListener() { + final View.OnClickListener alarmClickListener = new View.OnClickListener() { public void onClick(View v) { startActivity(new Intent(DeskClock.this, AlarmClock.class)); } - }); + }; + + mNextAlarm = (TextView) findViewById(R.id.nextAlarm); + mNextAlarm.setOnClickListener(alarmClickListener); + + final ImageButton alarmButton = (ImageButton) findViewById(R.id.alarm_button); + alarmButton.setOnClickListener(alarmClickListener); final ImageButton galleryButton = (ImageButton) findViewById(R.id.gallery_button); galleryButton.setOnClickListener(new View.OnClickListener() { @@ -612,7 +630,7 @@ public class DeskClock extends Activity { Intent.ACTION_VIEW, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI) .putExtra("slideshow", true) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP)); } catch (android.content.ActivityNotFoundException e) { Log.e(LOG_TAG, "Couldn't launch image browser", e); } @@ -625,7 +643,7 @@ public class DeskClock extends Activity { try { Intent musicAppQuery = getPackageManager() .getLaunchIntentForPackage(MUSIC_PACKAGE_ID) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP); if (musicAppQuery != null) { startActivity(musicAppQuery); } @@ -640,7 +658,7 @@ public class DeskClock extends Activity { public void onClick(View v) { startActivity( new Intent(Intent.ACTION_MAIN) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP) .addCategory(Intent.CATEGORY_HOME)); } }); @@ -667,7 +685,7 @@ public class DeskClock extends Activity { Intent genieAppQuery = getPackageManager() .getLaunchIntentForPackage(GENIE_PACKAGE_ID) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP); if (genieAppQuery != null) { startActivity(genieAppQuery); } -- cgit v1.2.3 From aa378715cf7b99264517250ac196c0fec7999d44 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Thu, 19 Nov 2009 16:06:27 -0500 Subject: Asynchronously handle TIME_TICK to not block the broadcast. BUG: 2268458 --- src/com/android/deskclock/DigitalClock.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DigitalClock.java b/src/com/android/deskclock/DigitalClock.java index 38ed91b5d..69a71565e 100644 --- a/src/com/android/deskclock/DigitalClock.java +++ b/src/com/android/deskclock/DigitalClock.java @@ -58,7 +58,12 @@ public class DigitalClock extends LinearLayout { Intent.ACTION_TIMEZONE_CHANGED)) { mCalendar = Calendar.getInstance(); } - updateTime(); + // Post a runnable to avoid blocking the broadcast. + mHandler.post(new Runnable() { + public void run() { + updateTime(); + } + }); } }; @@ -131,7 +136,7 @@ public class DigitalClock extends LinearLayout { filter.addAction(Intent.ACTION_TIME_TICK); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + mContext.registerReceiver(mIntentReceiver, filter); } /* monitor 12/24-hour display preference */ -- cgit v1.2.3 From ddacabc24b515db4fa6c7a289ef81d8050c8062c Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Mon, 23 Nov 2009 11:37:05 -0500 Subject: Add "add alarm" menu to the DeskClock activity. Rather than copying code from AlarmClock, there is now a version of addNewAlarm() that can be invoked from any activity. (AlarmClock now uses it, along with DeskClock.) Part of bug http://b/2278197 . --- src/com/android/deskclock/AlarmClock.java | 15 ++++++++++----- src/com/android/deskclock/DeskClock.java | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java index e872eadfd..4e4db1883 100644 --- a/src/com/android/deskclock/AlarmClock.java +++ b/src/com/android/deskclock/AlarmClock.java @@ -240,16 +240,21 @@ public class AlarmClock extends Activity implements OnItemClickListener { }); } - private void addNewAlarm() { - Uri uri = Alarms.addAlarm(getContentResolver()); - String segment = uri.getPathSegments().get(1); + // Version of addNewAlarm that can be called from any activity, e.g. DeskClock + protected static void addNewAlarm(Context context) { + final Uri uri = Alarms.addAlarm(context.getContentResolver()); + final String segment = uri.getPathSegments().get(1); int newId = Integer.parseInt(segment); if (Log.LOGV) { Log.v("In AlarmClock, new alarm id = " + newId); } - Intent intent = new Intent(this, SetAlarm.class); + final Intent intent = new Intent(context, SetAlarm.class); intent.putExtra(Alarms.ALARM_ID, newId); - startActivity(intent); + context.startActivity(intent); + } + + private void addNewAlarm() { + addNewAlarm(this); } @Override diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 0cd65a574..625f03ff4 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -708,6 +708,9 @@ public class DeskClock extends Activity { if (item.getItemId() == R.id.menu_item_alarms) { startActivity(new Intent(DeskClock.this, AlarmClock.class)); return true; + } else if (item.getItemId() == R.id.menu_item_add_alarm) { + AlarmClock.addNewAlarm(this); + return true; } return false; } -- cgit v1.2.3 From 98379599e76b778ae7e281d69c3f8923fa50cb27 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Fri, 27 Nov 2009 05:10:54 -0800 Subject: Fix issue 2192673: Music Pausing Even when notifications are set to silent. Do not play alarms if alarm stream volume is 0. --- src/com/android/deskclock/AlarmKlaxon.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/AlarmKlaxon.java b/src/com/android/deskclock/AlarmKlaxon.java index b8f4b6b25..040dc24df 100644 --- a/src/com/android/deskclock/AlarmKlaxon.java +++ b/src/com/android/deskclock/AlarmKlaxon.java @@ -229,10 +229,15 @@ public class AlarmKlaxon extends Service { private void startAlarm(MediaPlayer player) throws java.io.IOException, IllegalArgumentException, IllegalStateException { - player.setAudioStreamType(AudioManager.STREAM_ALARM); - player.setLooping(true); - player.prepare(); - player.start(); + final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); + // do not play alarms if stream volume is 0 + // (typically because ringer mode is silent). + if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { + player.setAudioStreamType(AudioManager.STREAM_ALARM); + player.setLooping(true); + player.prepare(); + player.start(); + } } private void setDataSourceFromResource(Resources resources, -- cgit v1.2.3 From 90c4833f94f4775f747333f8bd67107364a1d9c1 Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Mon, 23 Nov 2009 14:46:44 -0500 Subject: UI changes based on feedback from Jeff and Amar. Bug: 2278206 --- src/com/android/deskclock/AlarmClock.java | 20 +++++----- .../deskclock/DontPressWithParentLayout.java | 43 +++++++++++++++++++++ src/com/android/deskclock/SetAlarm.java | 45 ++++++++++++++-------- 3 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 src/com/android/deskclock/DontPressWithParentLayout.java (limited to 'src') diff --git a/src/com/android/deskclock/AlarmClock.java b/src/com/android/deskclock/AlarmClock.java index 4e4db1883..adfe74096 100644 --- a/src/com/android/deskclock/AlarmClock.java +++ b/src/com/android/deskclock/AlarmClock.java @@ -86,7 +86,7 @@ public class AlarmClock extends Activity implements OnItemClickListener { public View newView(Context context, Cursor cursor, ViewGroup parent) { View ret = mFactory.inflate(R.layout.alarm_time, parent, false); - DigitalClock digitalClock = (DigitalClock)ret.findViewById(R.id.digitalClock); + DigitalClock digitalClock = (DigitalClock) ret.findViewById(R.id.digitalClock); digitalClock.setLive(false); if (Log.LOGV) Log.v("newView " + cursor.getPosition()); return ret; @@ -108,14 +108,6 @@ public class AlarmClock extends Activity implements OnItemClickListener { (CheckBox) indicator.findViewById(R.id.clock_onoff); clockOnOff.setChecked(alarm.enabled); - // Handle the "checkbox" click and toggle the alarm. - clockOnOff.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - boolean isChecked = ((CheckBox) v).isChecked(); - updateIndicatorAndAlarm(isChecked, barOnOff, alarm); - } - }); - // Clicking outside the "checkbox" should also change the state. indicator.setOnClickListener(new OnClickListener() { public void onClick(View v) { @@ -230,6 +222,12 @@ public class AlarmClock extends Activity implements OnItemClickListener { addNewAlarm(); } }); + // Make the entire view selected when focused. + addAlarm.setOnFocusChangeListener(new View.OnFocusChangeListener() { + public void onFocusChange(View v, boolean hasFocus) { + v.setSelected(hasFocus); + } + }); ImageButton deskClock = (ImageButton) findViewById(R.id.desk_clock_button); @@ -291,6 +289,10 @@ public class AlarmClock extends Activity implements OnItemClickListener { // Set the custom view on the menu. menu.setHeaderView(v); + // Change the text based on the state of the alarm. + if (alarm.enabled) { + menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm); + } } @Override diff --git a/src/com/android/deskclock/DontPressWithParentLayout.java b/src/com/android/deskclock/DontPressWithParentLayout.java new file mode 100644 index 000000000..6c139983a --- /dev/null +++ b/src/com/android/deskclock/DontPressWithParentLayout.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009 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.deskclock; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + +/** + * Special class to to allow the parent to be pressed without being pressed + * itself. This way the time in the alarm list can be pressed without changing + * the background of the indicator. + */ +public class DontPressWithParentLayout extends LinearLayout { + + public DontPressWithParentLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setPressed(boolean pressed) { + // If the parent is pressed, do not set to pressed. + if (pressed && ((View) getParent()).isPressed()) { + return; + } + super.setPressed(pressed); + } +} diff --git a/src/com/android/deskclock/SetAlarm.java b/src/com/android/deskclock/SetAlarm.java index 7d84bb493..ce07cc15c 100644 --- a/src/com/android/deskclock/SetAlarm.java +++ b/src/com/android/deskclock/SetAlarm.java @@ -16,8 +16,10 @@ package com.android.deskclock; +import android.app.AlertDialog; import android.app.TimePickerDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; @@ -47,15 +49,14 @@ public class SetAlarm extends PreferenceActivity implements TimePickerDialog.OnTimeSetListener { private EditTextPreference mLabel; + private CheckBoxPreference mEnabledPref; private Preference mTimePref; private AlarmPreference mAlarmPref; private CheckBoxPreference mVibratePref; private RepeatPreference mRepeatPref; - private MenuItem mDeleteAlarmItem; private MenuItem mTestAlarmItem; private int mId; - private boolean mEnabled; private int mHour; private int mMinutes; @@ -80,6 +81,7 @@ public class SetAlarm extends PreferenceActivity return true; } }); + mEnabledPref = (CheckBoxPreference) findPreference("enabled"); mTimePref = findPreference("time"); mAlarmPref = (AlarmPreference) findPreference("alarm"); mVibratePref = (CheckBoxPreference) findPreference("vibrate"); @@ -93,7 +95,7 @@ public class SetAlarm extends PreferenceActivity /* load alarm details from database */ Alarm alarm = Alarms.getAlarm(getContentResolver(), mId); - mEnabled = alarm.enabled; + mEnabledPref.setChecked(alarm.enabled); mLabel.setText(alarm.label); mLabel.setSummary(alarm.label); mHour = alarm.hour; @@ -147,6 +149,12 @@ public class SetAlarm extends PreferenceActivity finish(); } }); + b = (Button) v.findViewById(R.id.alarm_delete); + b.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + deleteAlarm(); + } + }); // Replace the old content view with our new one. setContentView(ll); @@ -174,7 +182,7 @@ public class SetAlarm extends PreferenceActivity mMinutes = minute; updateTime(); // If the time has been changed, enable the alarm. - mEnabled = true; + mEnabledPref.setChecked(true); } private void updateTime() { @@ -187,15 +195,30 @@ public class SetAlarm extends PreferenceActivity private void saveAlarm() { final String alert = mAlarmPref.getAlertString(); - long time = Alarms.setAlarm(this, mId, mEnabled, mHour, mMinutes, - mRepeatPref.getDaysOfWeek(), mVibratePref.isChecked(), + long time = Alarms.setAlarm(this, mId, mEnabledPref.isChecked(), mHour, + mMinutes, mRepeatPref.getDaysOfWeek(), mVibratePref.isChecked(), mLabel.getText(), alert); - if (mEnabled) { + if (mEnabledPref.isChecked()) { popAlarmSetToast(this, time); } } + private void deleteAlarm() { + new AlertDialog.Builder(this) + .setTitle(getString(R.string.delete_alarm)) + .setMessage(getString(R.string.delete_alarm_confirm)) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface d, int w) { + Alarms.deleteAlarm(SetAlarm.this, mId); + finish(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + /** * Write alarm out to persistent store and pops toast if alarm * enabled. @@ -273,9 +296,6 @@ public class SetAlarm extends PreferenceActivity public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - mDeleteAlarmItem = menu.add(0, 0, 0, R.string.delete_alarm); - mDeleteAlarmItem.setIcon(android.R.drawable.ic_menu_delete); - if (AlarmClock.DEBUG) { mTestAlarmItem = menu.add(0, 0, 0, "test alarm"); } @@ -284,11 +304,6 @@ public class SetAlarm extends PreferenceActivity } public boolean onOptionsItemSelected(MenuItem item) { - if (item == mDeleteAlarmItem) { - Alarms.deleteAlarm(this, mId); - finish(); - return true; - } if (AlarmClock.DEBUG) { if (item == mTestAlarmItem) { setTestAlarm(); -- cgit v1.2.3 From 0a8adab905b45d7d7c6a4c467a4530440276bacd Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Tue, 1 Dec 2009 17:23:59 -0500 Subject: Reduce screen saver delay to 5 min. Discussion in http://b/2282444 and http://b/2227579 --- src/com/android/deskclock/DeskClock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 625f03ff4..fb7ce00cb 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -100,7 +100,7 @@ public class DeskClock extends Activity { private final long QUERY_WEATHER_DELAY = 5 * 60 * 1000; // 5 min // Delay before engaging the burn-in protection mode (green-on-black). - private final long SCREEN_SAVER_TIMEOUT = 10 * 60 * 1000; // 10 min + private final long SCREEN_SAVER_TIMEOUT = 5* 60 * 1000; // 10 min // Repositioning delay in screen saver. private final long SCREEN_SAVER_MOVE_DELAY = 60 * 1000; // 1 min -- cgit v1.2.3 From c1d449d071703d3c2b8c4d9961e812b4632b5c90 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 4 Dec 2009 14:47:03 -0500 Subject: Fix http://b/2305223 (DO NOT MERGE) When placed into night mode (dim mode), the DeskClock must restore full brightness any time the user returns to it from another activity. There are two reasons for this: 1. The other activity won't be dim, so it's sort of jarring to return to an almost-black screen. 2. As indicated in bug 2305223, visiting another activity is a useful way to restore the display if it's so dark that you can't see it to find the nightmode toggle button (e.g., ambient lighting is very bright). --- src/com/android/deskclock/DeskClock.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index fb7ce00cb..3cfcc38d7 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -540,7 +540,10 @@ public class DeskClock extends Activity { am.setRepeating(AlarmManager.RTC, today.getTimeInMillis(), AlarmManager.INTERVAL_DAY, mMidnightIntent); registerReceiver(mIntentReceiver, filter); - doDim(false); // un-dim when resuming + // un-dim when resuming + mDimmed = false; + doDim(false); + restoreScreen(); // disable screen saver refreshAll(); // will schedule periodic weather fetch -- cgit v1.2.3 From 7e827acae69298441b970262a309a957c92da155 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Thu, 3 Dec 2009 11:44:30 -0500 Subject: =?UTF-8?q?Show=20a=20long=20dash=20instead=20of=200=C2=B0=20if=20?= =?UTF-8?q?no=20temperature=20is=20available.=20(DO=20NOT=20MERGE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes http://b/2301604 --- src/com/android/deskclock/DeskClock.java | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 3cfcc38d7..88030cd4d 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -389,21 +389,35 @@ public class DeskClock extends Activity { mWeatherIconDrawable = mGenieResources.getDrawable(cur.getInt( cur.getColumnIndexOrThrow("iconResId"))); - mWeatherCurrentTemperatureString = String.format("%d\u00b0", - (cur.getInt(cur.getColumnIndexOrThrow("temperature")))); - mWeatherHighTemperatureString = String.format("%d\u00b0", - (cur.getInt(cur.getColumnIndexOrThrow("highTemperature")))); - mWeatherLowTemperatureString = String.format("%d\u00b0", - (cur.getInt(cur.getColumnIndexOrThrow("lowTemperature")))); + mWeatherLocationString = cur.getString( cur.getColumnIndexOrThrow("location")); + + // any of these may be NULL + final int colTemp = cur.getColumnIndexOrThrow("temperature"); + final int colHigh = cur.getColumnIndexOrThrow("highTemperature"); + final int colLow = cur.getColumnIndexOrThrow("lowTemperature"); + + mWeatherCurrentTemperatureString = + cur.isNull(colTemp) + ? "\u2014" + : String.format("%d\u00b0", cur.getInt(colTemp)); + mWeatherHighTemperatureString = + cur.isNull(colHigh) + ? "\u2014" + : String.format("%d\u00b0", cur.getInt(colHigh)); + mWeatherLowTemperatureString = + cur.isNull(colLow) + ? "\u2014" + : String.format("%d\u00b0", cur.getInt(colLow)); } else { Log.w(LOG_TAG, "No weather information available (cur=" + cur +")"); mWeatherIconDrawable = null; - mWeatherHighTemperatureString = ""; - mWeatherLowTemperatureString = ""; mWeatherLocationString = getString(R.string.weather_fetch_failure); + mWeatherCurrentTemperatureString = + mWeatherHighTemperatureString = + mWeatherLowTemperatureString = ""; } mHandy.sendEmptyMessage(UPDATE_WEATHER_DISPLAY_MSG); -- cgit v1.2.3 From 3d4de660d654fee760cf96f609198489e4d6525d Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 11 Dec 2009 02:07:36 -0500 Subject: Dismiss the desk clock if it was launched by docking. In other words: if the clock is behaving like a dock app (launched by a dock event), it should finish() when the device is removed from the dock. If, on the other hand, it's behaving like a regular app (launched from the Launcher), it should ignore an un-dock event. This change also removes support for entering the desk dock via a dialer code (an unnecessary feature since the app can always be invoked from the Launcher). Fixes http://b/2302215, approved by hiroshi. --- src/com/android/deskclock/DeskClock.java | 31 ++++++++-- src/com/android/deskclock/DockEventReceiver.java | 73 ------------------------ 2 files changed, 26 insertions(+), 78 deletions(-) delete mode 100644 src/com/android/deskclock/DockEventReceiver.java (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 88030cd4d..4c0217419 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -165,7 +165,7 @@ public class DeskClock extends Activity { private int mBatteryLevel = -1; private boolean mPluggedIn = false; - private boolean mInDock = false; + private boolean mLaunchedFromDock = false; private int mIdleTimeoutEpoch = 0; @@ -183,6 +183,16 @@ public class DeskClock extends Activity { handleBatteryUpdate( intent.getIntExtra("status", BATTERY_STATUS_UNKNOWN), intent.getIntExtra("level", 0)); + } else if (Intent.ACTION_DOCK_EVENT.equals(action)) { + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1); + if (DEBUG) Log.d(LOG_TAG, "ACTION_DOCK_EVENT, state=" + state); + if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + if (mLaunchedFromDock) { + // moveTaskToBack(false); + finish(); + } + mLaunchedFromDock = false; + } } } }; @@ -533,10 +543,20 @@ public class DeskClock extends Activity { win.setAttributes(winParams); } + @Override + public void onNewIntent(Intent newIntent) { + super.onNewIntent(newIntent); + if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent); + + // update our intent so that we can consult it to determine whether or + // not the most recent launch was via a dock event + setIntent(newIntent); + } + @Override public void onResume() { super.onResume(); - if (DEBUG) Log.d(LOG_TAG, "onResume"); + if (DEBUG) Log.d(LOG_TAG, "onResume with intent: " + getIntent()); // reload the date format in case the user has changed settings // recently @@ -545,14 +565,15 @@ public class DeskClock extends Activity { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_DATE_CHANGED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Intent.ACTION_DOCK_EVENT); filter.addAction(ACTION_MIDNIGHT); + registerReceiver(mIntentReceiver, filter); Calendar today = Calendar.getInstance(); today.add(Calendar.DATE, 1); mMidnightIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_MIDNIGHT), 0); AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); am.setRepeating(AlarmManager.RTC, today.getTimeInMillis(), AlarmManager.INTERVAL_DAY, mMidnightIntent); - registerReceiver(mIntentReceiver, filter); // un-dim when resuming mDimmed = false; @@ -571,13 +592,13 @@ public class DeskClock extends Activity { final boolean launchedFromDock = getIntent().hasCategory(Intent.CATEGORY_DESK_DOCK); - if (supportsWeather() && launchedFromDock && !mInDock) { + if (supportsWeather() && launchedFromDock && !mLaunchedFromDock) { // policy: fetch weather if launched via dock connection if (DEBUG) Log.d(LOG_TAG, "Device now docked; forcing weather to refresh right now"); requestWeatherDataFetch(); } - mInDock = launchedFromDock; + mLaunchedFromDock = launchedFromDock; } @Override diff --git a/src/com/android/deskclock/DockEventReceiver.java b/src/com/android/deskclock/DockEventReceiver.java deleted file mode 100644 index bfc4673d9..000000000 --- a/src/com/android/deskclock/DockEventReceiver.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2009 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.deskclock; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; - -/** - * BroadcastReceiver which receives {@link Intent#ACTION_DOCK_EVENT} events. - * Launches the CarDockActivity if the device is placed into a car dock. - * - * TODO: This is the wrong way to launch, as this would cause contention - * between multiple activities trying to launch if others did the same. Instead - * register for a regular intent which should fire when placed into a car dock. - */ -public class DockEventReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Intent clockIntent = new Intent(Intent.ACTION_MAIN); - clockIntent.setComponent( - new ComponentName(context, DeskClock.class)); - clockIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - String action = intent.getAction(); - if (Intent.ACTION_DOCK_EVENT.equals(action)) { - // Code to control a sticky notification for the dock. - /* - NotificationManager notificationManager = (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - - int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1); - if (dockState == Intent.EXTRA_DOCK_STATE_DESK) { - Notification n = new Notification(); - n.icon = R.drawable.notification; - n.defaults = Notification.DEFAULT_LIGHTS; - n.flags = Notification.FLAG_ONGOING_EVENT; - n.tickerText = context.getString(R.string.notification_title); - n.when = 0; - n.setLatestEventInfo( - context, - context.getString(R.string.notification_title), - context.getString(R.string.notification_text), - PendingIntent.getActivity(context, 0, clockIntent, 0)); - notificationManager.notify(0, n); - } else if (dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - notificationManager.cancelAll(); - } - */ - } else if (android.provider.Telephony.Intents.SECRET_CODE_ACTION.equals(action)) { - // The user dialed *#*#DESK#*#* - context.startActivity(clockIntent); - } - } -} -- cgit v1.2.3 From e7c9540b179fdafb69cdc5e05a2d769f4be9463a Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 8 Jan 2010 14:37:41 -0500 Subject: Refresh date display at midnight. Fixes http://b/2341789 --- src/com/android/deskclock/DeskClock.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 4c0217419..73a744873 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -177,7 +177,8 @@ public class DeskClock extends Activity { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (Intent.ACTION_DATE_CHANGED.equals(action)) { + if (DEBUG) Log.d(LOG_TAG, "mIntentReceiver.onReceive: action=" + action + ", intent=" + intent); + if (Intent.ACTION_DATE_CHANGED.equals(action) || ACTION_MIDNIGHT.equals(action)) { refreshDate(); } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { handleBatteryUpdate( @@ -570,10 +571,17 @@ public class DeskClock extends Activity { registerReceiver(mIntentReceiver, filter); Calendar today = Calendar.getInstance(); + today.set(Calendar.HOUR_OF_DAY, 0); + today.set(Calendar.MINUTE, 0); + today.set(Calendar.SECOND, 0); today.add(Calendar.DATE, 1); + long alarmTimeUTC = today.getTimeInMillis() + today.get(Calendar.ZONE_OFFSET); mMidnightIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_MIDNIGHT), 0); AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - am.setRepeating(AlarmManager.RTC, today.getTimeInMillis(), AlarmManager.INTERVAL_DAY, mMidnightIntent); + am.setRepeating(AlarmManager.RTC, alarmTimeUTC, AlarmManager.INTERVAL_DAY, mMidnightIntent); + if (DEBUG) Log.d(LOG_TAG, "set repeating midnight event at " + + alarmTimeUTC + " repeating every " + + AlarmManager.INTERVAL_DAY + " with intent: " + mMidnightIntent); // un-dim when resuming mDimmed = false; -- cgit v1.2.3 From 6e37178770f41ffcf8013e9460170140bcc6c130 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Thu, 10 Dec 2009 13:05:40 -0500 Subject: Re-schedule screen saver mode when returning to normal. Fixes http://b/2317620 --- src/com/android/deskclock/DeskClock.java | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 73a744873..3b3ce44f1 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -167,8 +167,6 @@ public class DeskClock extends Activity { private boolean mLaunchedFromDock = false; - private int mIdleTimeoutEpoch = 0; - private Random mRNG; private PendingIntent mMidnightIntent; @@ -207,9 +205,7 @@ public class DeskClock extends Activity { } else if (m.what == UPDATE_WEATHER_DISPLAY_MSG) { updateWeatherDisplay(); } else if (m.what == SCREEN_SAVER_TIMEOUT_MSG) { - if (m.arg1 == mIdleTimeoutEpoch) { - saveScreen(); - } + saveScreen(); } else if (m.what == SCREEN_SAVER_MOVE_MSG) { moveScreenSaver(); } @@ -264,6 +260,14 @@ public class DeskClock extends Activity { win.setAttributes(winParams); } + private void scheduleScreenSaver() { + // reschedule screen saver + mHandy.removeMessages(SCREEN_SAVER_TIMEOUT_MSG); + mHandy.sendMessageDelayed( + Message.obtain(mHandy, SCREEN_SAVER_TIMEOUT_MSG), + SCREEN_SAVER_TIMEOUT); + } + private void restoreScreen() { if (!mScreenSaverMode) return; if (DEBUG) Log.d(LOG_TAG, "restoreScreen"); @@ -272,6 +276,9 @@ public class DeskClock extends Activity { doDim(false); // restores previous dim mode // policy: update weather info when returning from screen saver if (mPluggedIn) requestWeatherDataFetch(); + + scheduleScreenSaver(); + refreshAll(); } @@ -592,10 +599,7 @@ public class DeskClock extends Activity { setWakeLock(mPluggedIn); - mIdleTimeoutEpoch++; - mHandy.sendMessageDelayed( - Message.obtain(mHandy, SCREEN_SAVER_TIMEOUT_MSG, mIdleTimeoutEpoch, 0), - SCREEN_SAVER_TIMEOUT); + scheduleScreenSaver(); final boolean launchedFromDock = getIntent().hasCategory(Intent.CATEGORY_DESK_DOCK); @@ -613,7 +617,9 @@ public class DeskClock extends Activity { public void onPause() { if (DEBUG) Log.d(LOG_TAG, "onPause"); - // Turn off the screen saver. (But don't un-dim.) + // Turn off the screen saver and cancel any pending timeouts. + // (But don't un-dim.) + mHandy.removeMessages(SCREEN_SAVER_TIMEOUT_MSG); restoreScreen(); // Other things we don't want to be doing in the background. -- cgit v1.2.3 From 9fa4cc96ece43fdc6873fdafb8c8f6b3ea0cc1fd Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Tue, 19 Jan 2010 22:50:37 -0500 Subject: Add Dock Settings menu item to DeskClock. Bug: 2367275 --- src/com/android/deskclock/DeskClock.java | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/com/android/deskclock/DeskClock.java b/src/com/android/deskclock/DeskClock.java index 3b3ce44f1..de96e9b9a 100644 --- a/src/com/android/deskclock/DeskClock.java +++ b/src/com/android/deskclock/DeskClock.java @@ -94,6 +94,9 @@ public class DeskClock extends Activity { // Alarm action for midnight (so we can update the date display). private static final String ACTION_MIDNIGHT = "com.android.deskclock.MIDNIGHT"; + // Intent to broadcast for dock settings. + private static final String DOCK_SETTINGS_ACTION = "com.android.settings.DOCK_SETTINGS"; + // Interval between polls of the weather widget. Its refresh period is // likely to be much longer (~3h), but we want to pick up any changes // within 5 minutes. @@ -757,14 +760,19 @@ public class DeskClock extends Activity { @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.menu_item_alarms) { - startActivity(new Intent(DeskClock.this, AlarmClock.class)); - return true; - } else if (item.getItemId() == R.id.menu_item_add_alarm) { - AlarmClock.addNewAlarm(this); - return true; + switch (item.getItemId()) { + case R.id.menu_item_alarms: + startActivity(new Intent(DeskClock.this, AlarmClock.class)); + return true; + case R.id.menu_item_add_alarm: + AlarmClock.addNewAlarm(this); + return true; + case R.id.menu_item_dock_settings: + startActivity(new Intent(DOCK_SETTINGS_ACTION)); + return true; + default: + return false; } - return false; } @Override -- cgit v1.2.3