1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.alarmclock; 18 19import android.app.Activity; 20import android.app.AlertDialog; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.Intent; 24import android.content.SharedPreferences; 25import android.content.res.Configuration; 26import android.database.Cursor; 27import android.net.Uri; 28import android.os.Bundle; 29import android.os.Handler; 30import android.view.ContextMenu; 31import android.view.ContextMenu.ContextMenuInfo; 32import android.view.LayoutInflater; 33import android.view.Menu; 34import android.view.MenuItem; 35import android.view.View; 36import android.view.View.OnClickListener; 37import android.view.ViewGroup; 38import android.widget.AdapterView; 39import android.widget.AdapterView.AdapterContextMenuInfo; 40import android.widget.AdapterView.OnItemClickListener; 41import android.widget.CheckBox; 42import android.widget.CursorAdapter; 43import android.widget.ListView; 44import android.widget.TextView; 45 46import java.text.DateFormatSymbols; 47import java.util.Calendar; 48 49/** 50 * AlarmClock application. 51 */ 52public class AlarmClock extends Activity implements OnItemClickListener { 53 54 static final String PREFERENCES = "AlarmClock"; 55 static final String PREF_CLOCK_FACE = "face"; 56 static final String PREF_SHOW_CLOCK = "show_clock"; 57 58 /** Cap alarm count at this number */ 59 static final int MAX_ALARM_COUNT = 12; 60 61 /** This must be false for production. If true, turns on logging, 62 test code, etc. */ 63 static final boolean DEBUG = false; 64 65 private SharedPreferences mPrefs; 66 private LayoutInflater mFactory; 67 private ViewGroup mClockLayout; 68 private View mClock = null; 69 private ListView mAlarmsList; 70 private Cursor mCursor; 71 72 private String mAm, mPm; 73 74 /** 75 * Which clock face to show 76 */ 77 private int mFace = -1; 78 79 /* 80 * TODO: it would be nice for this to live in an xml config file. 81 */ 82 static final int[] CLOCKS = { 83 R.layout.clock_basic_bw, 84 R.layout.clock_googly, 85 R.layout.clock_droid2, 86 R.layout.clock_droids, 87 R.layout.digital_clock 88 }; 89 90 private class AlarmTimeAdapter extends CursorAdapter { 91 public AlarmTimeAdapter(Context context, Cursor cursor) { 92 super(context, cursor); 93 } 94 95 public View newView(Context context, Cursor cursor, ViewGroup parent) { 96 View ret = mFactory.inflate(R.layout.alarm_time, parent, false); 97 98 ((TextView) ret.findViewById(R.id.am)).setText(mAm); 99 ((TextView) ret.findViewById(R.id.pm)).setText(mPm); 100 101 DigitalClock digitalClock = 102 (DigitalClock) ret.findViewById(R.id.digitalClock); 103 digitalClock.setLive(false); 104 return ret; 105 } 106 107 public void bindView(View view, Context context, Cursor cursor) { 108 final Alarm alarm = new Alarm(cursor); 109 110 CheckBox onButton = (CheckBox) view.findViewById(R.id.alarmButton); 111 onButton.setChecked(alarm.enabled); 112 onButton.setOnClickListener(new OnClickListener() { 113 public void onClick(View v) { 114 boolean isChecked = ((CheckBox) v).isChecked(); 115 Alarms.enableAlarm(AlarmClock.this, alarm.id, 116 isChecked); 117 if (isChecked) { 118 SetAlarm.popAlarmSetToast(AlarmClock.this, 119 alarm.hour, alarm.minutes, alarm.daysOfWeek); 120 } 121 } 122 }); 123 124 DigitalClock digitalClock = 125 (DigitalClock) view.findViewById(R.id.digitalClock); 126 127 // set the alarm text 128 final Calendar c = Calendar.getInstance(); 129 c.set(Calendar.HOUR_OF_DAY, alarm.hour); 130 c.set(Calendar.MINUTE, alarm.minutes); 131 digitalClock.updateTime(c); 132 133 // Set the repeat text or leave it blank if it does not repeat. 134 TextView daysOfWeekView = 135 (TextView) digitalClock.findViewById(R.id.daysOfWeek); 136 final String daysOfWeekStr = 137 alarm.daysOfWeek.toString(AlarmClock.this, false); 138 if (daysOfWeekStr != null && daysOfWeekStr.length() != 0) { 139 daysOfWeekView.setText(daysOfWeekStr); 140 daysOfWeekView.setVisibility(View.VISIBLE); 141 } else { 142 daysOfWeekView.setVisibility(View.GONE); 143 } 144 145 // Display the label 146 TextView labelView = 147 (TextView) digitalClock.findViewById(R.id.label); 148 if (alarm.label != null && alarm.label.length() != 0) { 149 labelView.setText(alarm.label); 150 labelView.setVisibility(View.VISIBLE); 151 } else { 152 labelView.setVisibility(View.GONE); 153 } 154 } 155 }; 156 157 @Override 158 public boolean onContextItemSelected(final MenuItem item) { 159 final AdapterContextMenuInfo info = 160 (AdapterContextMenuInfo) item.getMenuInfo(); 161 final int id = (int) info.id; 162 switch (item.getItemId()) { 163 case R.id.delete_alarm: 164 // Confirm that the alarm will be deleted. 165 new AlertDialog.Builder(this) 166 .setTitle(getString(R.string.delete_alarm)) 167 .setMessage(getString(R.string.delete_alarm_confirm)) 168 .setPositiveButton(android.R.string.ok, 169 new DialogInterface.OnClickListener() { 170 public void onClick(DialogInterface d, 171 int w) { 172 Alarms.deleteAlarm(AlarmClock.this, id); 173 } 174 }) 175 .setNegativeButton(android.R.string.cancel, null) 176 .show(); 177 return true; 178 179 case R.id.enable_alarm: 180 final Cursor c = (Cursor) mAlarmsList.getAdapter() 181 .getItem(info.position); 182 final Alarm alarm = new Alarm(c); 183 Alarms.enableAlarm(this, alarm.id, !alarm.enabled); 184 if (!alarm.enabled) { 185 SetAlarm.popAlarmSetToast(this, alarm.hour, alarm.minutes, 186 alarm.daysOfWeek); 187 } 188 return true; 189 190 default: 191 break; 192 } 193 return super.onContextItemSelected(item); 194 } 195 196 @Override 197 protected void onCreate(Bundle icicle) { 198 super.onCreate(icicle); 199 200 String[] ampm = new DateFormatSymbols().getAmPmStrings(); 201 mAm = ampm[0]; 202 mPm = ampm[1]; 203 204 mFactory = LayoutInflater.from(this); 205 mPrefs = getSharedPreferences(PREFERENCES, 0); 206 mCursor = Alarms.getAlarmsCursor(getContentResolver()); 207 208 updateLayout(); 209 setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); 210 } 211 212 private final Handler mHandler = new Handler(); 213 214 @Override 215 public void onConfigurationChanged(Configuration newConfig) { 216 super.onConfigurationChanged(newConfig); 217 // Send a message to avoid a possible ANR. 218 mHandler.post(new Runnable() { 219 public void run() { 220 updateLayout(); 221 inflateClock(); 222 setClockVisibility(mPrefs.getBoolean(PREF_SHOW_CLOCK, true)); 223 } 224 }); 225 } 226 227 private void updateLayout() { 228 setContentView(R.layout.alarm_clock); 229 mAlarmsList = (ListView) findViewById(R.id.alarms_list); 230 mAlarmsList.setAdapter(new AlarmTimeAdapter(this, mCursor)); 231 mAlarmsList.setVerticalScrollBarEnabled(true); 232 mAlarmsList.setOnItemClickListener(this); 233 mAlarmsList.setOnCreateContextMenuListener(this); 234 235 mClockLayout = (ViewGroup) findViewById(R.id.clock_layout); 236 mClockLayout.setOnClickListener(new View.OnClickListener() { 237 public void onClick(View v) { 238 final Intent intent = 239 new Intent(AlarmClock.this, ClockPicker.class); 240 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 241 startActivity(intent); 242 } 243 }); 244 } 245 246 @Override 247 protected void onResume() { 248 super.onResume(); 249 250 int face = mPrefs.getInt(PREF_CLOCK_FACE, 0); 251 if (mFace != face) { 252 if (face < 0 || face >= AlarmClock.CLOCKS.length) { 253 mFace = 0; 254 } else { 255 mFace = face; 256 } 257 inflateClock(); 258 } 259 } 260 261 @Override 262 protected void onDestroy() { 263 super.onDestroy(); 264 ToastMaster.cancelToast(); 265 mCursor.deactivate(); 266 } 267 268 protected void inflateClock() { 269 if (mClock != null) { 270 mClockLayout.removeView(mClock); 271 } 272 273 LayoutInflater.from(this).inflate(CLOCKS[mFace], mClockLayout); 274 mClock = findViewById(R.id.clock); 275 276 TextView am = (TextView) findViewById(R.id.am); 277 TextView pm = (TextView) findViewById(R.id.pm); 278 279 if (am != null) { 280 am.setText(mAm); 281 } 282 if (pm != null) { 283 pm.setText(mPm); 284 } 285 } 286 287 @Override 288 public boolean onCreateOptionsMenu(Menu menu) { 289 // Inflate our menu. 290 getMenuInflater().inflate(R.menu.main_menu, menu); 291 292 return super.onCreateOptionsMenu(menu); 293 } 294 295 @Override 296 public void onCreateContextMenu(ContextMenu menu, View view, 297 ContextMenuInfo menuInfo) { 298 // Inflate the menu from xml. 299 getMenuInflater().inflate(R.menu.context_menu, menu); 300 301 // Use the current item to create a custom view for the header. 302 final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; 303 final Cursor c = 304 (Cursor) mAlarmsList.getAdapter().getItem((int) info.position); 305 final Alarm alarm = new Alarm(c); 306 307 // Construct the Calendar to compute the time. 308 final Calendar cal = Calendar.getInstance(); 309 cal.set(Calendar.HOUR_OF_DAY, alarm.hour); 310 cal.set(Calendar.MINUTE, alarm.minutes); 311 final String time = Alarms.formatTime(this, cal); 312 313 // Inflate the custom view and set each TextView's text. 314 final View v = mFactory.inflate(R.layout.context_menu_header, null); 315 TextView textView = (TextView) v.findViewById(R.id.header_time); 316 textView.setText(time); 317 textView = (TextView) v.findViewById(R.id.header_label); 318 textView.setText(alarm.label); 319 320 // Set the custom view on the menu. 321 menu.setHeaderView(v); 322 // Change the text to "disable" if the alarm is already enabled. 323 if (alarm.enabled) { 324 menu.findItem(R.id.enable_alarm).setTitle(R.string.disable_alarm); 325 } 326 } 327 328 public void onItemClick(AdapterView parent, View v, int pos, long id) { 329 Intent intent = new Intent(this, SetAlarm.class); 330 intent.putExtra(Alarms.ALARM_ID, (int) id); 331 startActivity(intent); 332 } 333 334 /** 335 * Only allow user to add a new alarm if there are fewer than 336 * MAX_ALARM_COUNT 337 */ 338 @Override 339 public boolean onPrepareOptionsMenu(Menu menu) { 340 menu.findItem(R.id.menu_add_alarm).setVisible( 341 mAlarmsList.getAdapter().getCount() < MAX_ALARM_COUNT); 342 menu.findItem(R.id.menu_toggle_clock).setTitle( 343 getClockVisibility() ? R.string.hide_clock 344 : R.string.show_clock); 345 return super.onPrepareOptionsMenu(menu); 346 } 347 348 @Override 349 public boolean onOptionsItemSelected(MenuItem item) { 350 switch (item.getItemId()) { 351 case R.id.menu_add_alarm: 352 Uri uri = Alarms.addAlarm(getContentResolver()); 353 // TODO: Create new alarm _after_ SetAlarm so the user has the 354 // chance to cancel alarm creation. 355 String segment = uri.getPathSegments().get(1); 356 int newId = Integer.parseInt(segment); 357 if (Log.LOGV) { 358 Log.v("In AlarmClock, new alarm id = " + newId); 359 } 360 Intent intent = new Intent(this, SetAlarm.class); 361 intent.putExtra(Alarms.ALARM_ID, newId); 362 startActivity(intent); 363 return true; 364 365 case R.id.menu_toggle_clock: 366 setClockVisibility(!getClockVisibility()); 367 saveClockVisibility(); 368 return true; 369 370 case R.id.menu_settings: 371 startActivity(new Intent(this, SettingsActivity.class)); 372 return true; 373 } 374 375 return super.onOptionsItemSelected(item); 376 } 377 378 379 private boolean getClockVisibility() { 380 return mClockLayout.getVisibility() == View.VISIBLE; 381 } 382 383 private void setClockVisibility(boolean visible) { 384 mClockLayout.setVisibility(visible ? View.VISIBLE : View.GONE); 385 } 386 387 private void saveClockVisibility() { 388 mPrefs.edit().putBoolean(PREF_SHOW_CLOCK, getClockVisibility()).commit(); 389 } 390} 391