ページ

2014/03/05

[Android]日付や時刻・タイムゾーンの変更を検知するBroadcastReceiver

日時変更イベントで AppWidget を更新したかったので
日付や時刻・タイムゾーンの変更を検知する BroadcastReceiver を作ってみました。

時刻・タイムゾーンの変更を検知する

Intent#ACTION_TIME_CHANGED | Android Developers
Intent#ACTION_TIMEZONE_CHANGED | Android Developers
@Override
public void onReceive(Context context, Intent intent){
    String action = intent.getAction();
    if(Intent.ACTION_TIME_CHANGED.equals(action) || Intent.ACTION_TIMEZONE_CHANGED.equals(action)){
        // onTimeChanged!
    }
}
ユーザの設定で自動設定(ネットワークから提供されたタイムゾーン/日時を使用する設定)になっている場合は
接続するネットワークが変わると変更イベントが飛ぶので注意です。

日付の変更を検知する

Intent#ACTION_DATE_CHANGED | Android Developers というものがあるので
同じように行けるかと思いきや何故か検知することができません。
日付変更イベントが飛んでいないのか検知できないのかわかりませんが、
検知できないのなら自分で飛ばして自分で検知しよう!というのが一般的な対応策のようです。
AlarmManager#setAlarmManager#setExactで 0 時 0 分にイベントが飛ぶように設定して
検知する度に翌日にセットし直してやれば毎日日付変更のイベントが飛んで検知することができます。
@Override
public void onReceive(Context context, Intent intent){
    String action = intent.getAction();
    if(getDateChangedAction(context).equals(action)){
        onDateChanged(context);
        registerDateChangeReceiver(context);
    }
}

/**
 * 翌日の0時に {@link AlarmManager} をセットする
 */
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void registerDateChangeReceiver(Context context){
    unregisterDateChangeReceiver(context);
    Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    calendar.add(Calendar.DAY_OF_MONTH, 1);
    AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    PendingIntent pendingIntent = createDateChangePendingIntent(context);
    if(AndroidUtils.isLessThanBuildVersion(Build.VERSION_CODES.KITKAT)){
        alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
    }else{
        alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
    }
}

/**
 * すでにセットされた Alarm を解除する
 */
public static void unregisterDateChangeReceiver(Context context){
    AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    alarmManager.cancel(createDateChangePendingIntent(context));
}

/**
 * 日付変更の PendingIntent を生成する
 */
private static PendingIntent createDateChangePendingIntent(Context context){
    Intent intent = new Intent(getDateChangedAction(context));
    return PendingIntent.getBroadcast(context, 0, intent, 0);
}

public static String getDateChangedAction(Context context){
    return context.getPackageName() + ".action.DATE_CHANGED";
}
参考
Taosoftware: AlarmManager1 Android でCronみたいなことをするには
AlarmManagerがKitKatで省エネ動作になった - _development,

AndroidManifest.xml

<receiver
    android:name=".YourDateTimeChangedReceiver"
    >
    <intent-filter>
        <action android:name="your.package.name.action.DATE_CHANGED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.TIMEZONE_CHANGED" />
        <action android:name="android.intent.action.TIME_SET" />
    </intent-filter>
</receiver>
AndroidManifest.xml はこんな感じです。

ソースコードは wada811/AndroidLibrary@wada811 に追加しました。