1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.widget;
18
19import com.android.internal.R;
20
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.database.ContentObserver;
26import android.graphics.Typeface;
27import android.os.Handler;
28import android.provider.Settings;
29import android.text.format.DateFormat;
30import android.util.AttributeSet;
31import android.view.View;
32import android.widget.LinearLayout;
33import android.widget.TextView;
34
35import java.lang.ref.WeakReference;
36import java.text.DateFormatSymbols;
37import java.util.Calendar;
38
39/**
40 * Displays the time
41 */
42public class DigitalClock extends LinearLayout {
43
44    private final static String M12 = "h:mm";
45    private final static String M24 = "kk:mm";
46
47    private Calendar mCalendar;
48    private String mFormat;
49    private TextView mTimeDisplay;
50    private AmPm mAmPm;
51    private ContentObserver mFormatChangeObserver;
52    private int mAttached = 0; // for debugging - tells us whether attach/detach is unbalanced
53
54    /* called by system on minute ticks */
55    private final Handler mHandler = new Handler();
56    private BroadcastReceiver mIntentReceiver;
57
58    private static class TimeChangedReceiver extends BroadcastReceiver {
59        private WeakReference<DigitalClock> mClock;
60        private Context mContext;
61
62        public TimeChangedReceiver(DigitalClock clock) {
63            mClock = new WeakReference<DigitalClock>(clock);
64            mContext = clock.getContext();
65        }
66
67        @Override
68        public void onReceive(Context context, Intent intent) {
69            // Post a runnable to avoid blocking the broadcast.
70            final boolean timezoneChanged =
71                    intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED);
72            final DigitalClock clock = mClock.get();
73            if (clock != null) {
74                clock.mHandler.post(new Runnable() {
75                    public void run() {
76                        if (timezoneChanged) {
77                            clock.mCalendar = Calendar.getInstance();
78                        }
79                        clock.updateTime();
80                    }
81                });
82            } else {
83                try {
84                    mContext.unregisterReceiver(this);
85                } catch (RuntimeException e) {
86                    // Shouldn't happen
87                }
88            }
89        }
90    };
91
92    static class AmPm {
93        private TextView mAmPm;
94        private String mAmString, mPmString;
95
96        AmPm(View parent, Typeface tf) {
97            mAmPm = (TextView) parent.findViewById(R.id.am_pm);
98            if (tf != null) {
99                mAmPm.setTypeface(tf);
100            }
101
102            String[] ampm = new DateFormatSymbols().getAmPmStrings();
103            mAmString = ampm[0];
104            mPmString = ampm[1];
105        }
106
107        void setShowAmPm(boolean show) {
108            mAmPm.setVisibility(show ? View.VISIBLE : View.GONE);
109        }
110
111        void setIsMorning(boolean isMorning) {
112            mAmPm.setText(isMorning ? mAmString : mPmString);
113        }
114    }
115
116    private static class FormatChangeObserver extends ContentObserver {
117        private WeakReference<DigitalClock> mClock;
118        private Context mContext;
119        public FormatChangeObserver(DigitalClock clock) {
120            super(new Handler());
121            mClock = new WeakReference<DigitalClock>(clock);
122            mContext = clock.getContext();
123        }
124        @Override
125        public void onChange(boolean selfChange) {
126            DigitalClock digitalClock = mClock.get();
127            if (digitalClock != null) {
128                digitalClock.setDateFormat();
129                digitalClock.updateTime();
130            } else {
131                try {
132                    mContext.getContentResolver().unregisterContentObserver(this);
133                } catch (RuntimeException e) {
134                    // Shouldn't happen
135                }
136            }
137        }
138    }
139
140    public DigitalClock(Context context) {
141        this(context, null);
142    }
143
144    public DigitalClock(Context context, AttributeSet attrs) {
145        super(context, attrs);
146    }
147
148    @Override
149    protected void onFinishInflate() {
150        super.onFinishInflate();
151
152        mTimeDisplay = (TextView) findViewById(R.id.timeDisplay);
153        mTimeDisplay.setTypeface(Typeface.createFromFile("/system/fonts/Clockopia.ttf"));
154        mAmPm = new AmPm(this, Typeface.createFromFile("/system/fonts/DroidSans-Bold.ttf"));
155        mCalendar = Calendar.getInstance();
156
157        setDateFormat();
158    }
159
160    @Override
161    protected void onAttachedToWindow() {
162        super.onAttachedToWindow();
163
164        mAttached++;
165
166        /* monitor time ticks, time changed, timezone */
167        if (mIntentReceiver == null) {
168            mIntentReceiver = new TimeChangedReceiver(this);
169            IntentFilter filter = new IntentFilter();
170            filter.addAction(Intent.ACTION_TIME_TICK);
171            filter.addAction(Intent.ACTION_TIME_CHANGED);
172            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
173            mContext.registerReceiver(mIntentReceiver, filter);
174        }
175
176        /* monitor 12/24-hour display preference */
177        if (mFormatChangeObserver == null) {
178            mFormatChangeObserver = new FormatChangeObserver(this);
179            mContext.getContentResolver().registerContentObserver(
180                    Settings.System.CONTENT_URI, true, mFormatChangeObserver);
181        }
182
183        updateTime();
184    }
185
186    @Override
187    protected void onDetachedFromWindow() {
188        super.onDetachedFromWindow();
189
190        mAttached--;
191
192        if (mIntentReceiver != null) {
193            mContext.unregisterReceiver(mIntentReceiver);
194        }
195        if (mFormatChangeObserver != null) {
196            mContext.getContentResolver().unregisterContentObserver(
197                    mFormatChangeObserver);
198        }
199
200        mFormatChangeObserver = null;
201        mIntentReceiver = null;
202    }
203
204    void updateTime(Calendar c) {
205        mCalendar = c;
206        updateTime();
207    }
208
209    private void updateTime() {
210        mCalendar.setTimeInMillis(System.currentTimeMillis());
211
212        CharSequence newTime = DateFormat.format(mFormat, mCalendar);
213        mTimeDisplay.setText(newTime);
214        mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0);
215    }
216
217    private void setDateFormat() {
218        mFormat = android.text.format.DateFormat.is24HourFormat(getContext())
219            ? M24 : M12;
220        mAmPm.setShowAmPm(mFormat.equals(M12));
221    }
222}
223