/* * Copyright (C) 2014 Hippo Seven * * 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.hippo.widget; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.SystemClock; import android.provider.Settings; import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.ViewDebug.ExportedProperty; import android.widget.TextView; import java.util.Calendar; import java.util.TimeZone; public class TextClock extends TextView { public static final CharSequence DEFAULT_FORMAT_12_HOUR = "hh:mm a"; public static final CharSequence DEFAULT_FORMAT_24_HOUR; static { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) DEFAULT_FORMAT_24_HOUR = "kk:mm"; else DEFAULT_FORMAT_24_HOUR = "HH:mm"; } private CharSequence mFormat12; private CharSequence mFormat24; @ExportedProperty private CharSequence mFormat; @ExportedProperty private boolean mHasSeconds; private boolean mAttached; private Calendar mTime; private String mTimeZone; private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { chooseFormat(); onTimeChanged(); } @Override public void onChange(boolean selfChange, Uri uri) { chooseFormat(); onTimeChanged(); } }; private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { final String timeZone = intent.getStringExtra("time-zone"); createTime(timeZone); } onTimeChanged(); } }; private final Runnable mTicker = new Runnable() { @Override public void run() { onTimeChanged(); long now = SystemClock.uptimeMillis(); long next = now + (1000 - now % 1000); getHandler().postAtTime(mTicker, next); } }; public TextClock(Context context) { this(context, null); } public TextClock(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TextClock(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mFormat12 = DEFAULT_FORMAT_12_HOUR; mFormat24 = DEFAULT_FORMAT_24_HOUR; mTimeZone = TimeZone.getDefault().getID(); init(); } private void init() { createTime(mTimeZone); // Wait until onAttachedToWindow() to handle the ticker chooseFormat(false); } private void createTime(String timeZone) { if (timeZone != null) { mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); } else { mTime = Calendar.getInstance(); } } public CharSequence getFormat12Hour() { return mFormat12; } public void setFormat12Hour(CharSequence format) { mFormat12 = format; chooseFormat(); onTimeChanged(); } public CharSequence getFormat24Hour() { return mFormat24; } public void setFormat24Hour(CharSequence format) { mFormat24 = format; chooseFormat(); onTimeChanged(); } public boolean is24HourModeEnabled() { return DateFormat.is24HourFormat(getContext()); } public String getTimeZone() { return mTimeZone; } public void setTimeZone(String timeZone) { mTimeZone = timeZone; createTime(timeZone); onTimeChanged(); } /** * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} * depending on whether the user has selected 24-hour format. * * Calling this method does not schedule or unschedule the time ticker. */ private void chooseFormat() { chooseFormat(true); } public CharSequence getFormat() { return mFormat; } /** * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} * depending on whether the user has selected 24-hour format. * * @param handleTicker true if calling this method should schedule/unschedule the * time ticker, false otherwise */ private void chooseFormat(boolean handleTicker) { final boolean format24Requested = is24HourModeEnabled(); if (format24Requested) { mFormat = mFormat24; } else { mFormat = mFormat12; } boolean hadSeconds = mHasSeconds; mHasSeconds = DateUtils.hasSeconds(mFormat); if (handleTicker && mAttached && hadSeconds != mHasSeconds) { if (hadSeconds) getHandler().removeCallbacks(mTicker); else mTicker.run(); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (!mAttached) { mAttached = true; registerReceiver(); registerObserver(); createTime(mTimeZone); if (mHasSeconds) { mTicker.run(); } else { onTimeChanged(); } } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mAttached) { unregisterReceiver(); unregisterObserver(); getHandler().removeCallbacks(mTicker); mAttached = false; } } private void registerReceiver() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_TICK); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); } private void registerObserver() { final ContentResolver resolver = getContext().getContentResolver(); resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); } private void unregisterReceiver() { getContext().unregisterReceiver(mIntentReceiver); } private void unregisterObserver() { final ContentResolver resolver = getContext().getContentResolver(); resolver.unregisterContentObserver(mFormatChangeObserver); } private void onTimeChanged() { mTime.setTimeInMillis(System.currentTimeMillis()); setText(DateFormat.format(mFormat, mTime)); } }