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