com.ksk.droidbatterybooster.provider.TimeSchedule.java Source code

Java tutorial

Introduction

Here is the source code for com.ksk.droidbatterybooster.provider.TimeSchedule.java

Source

/*
 * Copyright (C) 2014 IUH yber$oft Team
 * 
 * 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.ksk.droidbatterybooster.provider;

import java.text.DateFormatSymbols;
import java.util.Calendar;

import com.ksk.droidbatterybooster.R;

import vn.cybersoft.obs.android.utilities.Log;
import vn.cybersoft.obs.android.utilities.Utils;
import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Settings;
import android.support.v4.content.CursorLoader;
import android.text.format.DateFormat;

/**
 * @author Atom
 *
 */
public class TimeSchedule implements Parcelable, DataProviderApi.TimeSchedulesColumns {
    // This action triggers the ScheduleReceiver as well as the TimeScheduleExcutorService. It
    // is a public action used in the manifest for receiving TimeSchedule broadcasts
    // from the alarm manager.
    public static final String EXECUTE_SCHEDULE_ACTION = "vn.cybersoft.obs.android.intent.action.EXECUTE_TIME_SCHEDULE";

    // This extra is the raw TimeSchedule object data. It is used in the
    // AlarmManagerService to avoid a ClassNotFoundException when filling in
    // the Intent extras.
    public static final String INTENT_RAW_DATA = "intent.extra.schedule_raw";

    // This string is used when passing an schedules object through an intent.
    public static final String INTENT_EXTRA = "intent.extra.schedule";

    // This string is used to identify the schedule id passed to SetTimeSchedule from the
    // list of time schedules.
    public static final String EXTRA_ID = "time_schedule_id";

    private final static String DM12 = "E h:mm aa";
    private final static String DM24 = "E k:mm";

    private final static String M12 = "h:mm aa";

    public final static String M24 = "kk:mm";

    /**
     * schedule start with an invalid id when it hasn't been saved to the database.
     */
    public static final long INVALID_ID = -1;

    // Used when filtering enabled schedules.
    public static final String WHERE_ENABLED = ENABLED + "=1";

    /**
     * The default sort order for this table
     */
    public static final String DEFAULT_SORT_ORDER = HOUR + ", " + MINUTES + " ASC";

    public static final String[] QUERY_COLUMNS = { _ID, HOUR, MINUTES, DAYS_OF_WEEK, SCHEDULE_TIME, ENABLED,
            MODE_ID };

    /**
     * These save calls to cursor.getColumnIndexOrThrow()
     * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS
     */
    public static final int ID_INDEX = 0;
    public static final int HOUR_INDEX = 1;
    public static final int MINUTES_INDEX = 2;
    public static final int DAYS_OF_WEEK_INDEX = 3;
    public static final int SCHEDULE_TIME_INDEX = 4;
    public static final int ENABLED_INDEX = 5;
    public static final int MODE_ID_INDEX = 6;

    private static final int COLUMN_COUNT = MODE_ID_INDEX + 1;

    private static ContentValues createContentValues(TimeSchedule schedule) {
        ContentValues values = new ContentValues(COLUMN_COUNT);
        // Set the schedule_time value if this schedule does not repeat. This will be
        // used later to disable expire schedules.
        @SuppressWarnings("unused")
        long time = 0;
        if (!schedule.daysOfWeek.isRepeatSet()) {
            time = calculateTimeSchedule(schedule);
        }

        values.put(ENABLED, schedule.enabled ? 1 : 0);
        values.put(HOUR, schedule.hour);
        values.put(MINUTES, schedule.minutes);
        values.put(SCHEDULE_TIME, schedule.time);
        values.put(DAYS_OF_WEEK, schedule.daysOfWeek.getCoded());
        values.put(MODE_ID, schedule.modeId);

        return values;
    }

    public static Uri getUri(int scheduleId) {
        return ContentUris.withAppendedId(CONTENT_URI, scheduleId);
    }

    public static long getId(Uri contentUri) {
        return ContentUris.parseId(contentUri);
    }

    public static CursorLoader getSchedulesCursorLoader(Context context) {
        return new CursorLoader(context, CONTENT_URI, QUERY_COLUMNS, null, null, DEFAULT_SORT_ORDER);
    }

    /**
     * Creates a new time schedule and fills in the given schedule's id.
     */
    public static long addTimeSchedule(Context context, TimeSchedule schedule) {
        ContentValues values = createContentValues(schedule);
        Uri uri = context.getContentResolver().insert(CONTENT_URI, values);
        schedule.id = (int) ContentUris.parseId(uri);
        setNextAction(context);
        return calculateTimeSchedule(schedule);
    }

    public static void deleteTimeSchedule(Context context, long scheduleId) {
        if (scheduleId == -1)
            return;

        ContentResolver contentResolver = context.getContentResolver();

        Uri uri = ContentUris.withAppendedId(CONTENT_URI, scheduleId);
        contentResolver.delete(uri, "", null);

        setNextAction(context);
    }

    /**
     * Queries all time schedules
     * @return cursor over all schedules
     */
    public static Cursor getTimeSchedulesCursor(ContentResolver contentResolver) {
        return contentResolver.query(CONTENT_URI, QUERY_COLUMNS, null, null, DEFAULT_SORT_ORDER);
    }

    // Private method to get a more limited set of schedules from the database.
    private static Cursor getFilteredTimeSchedulesCursor(ContentResolver contentResolver) {
        return contentResolver.query(CONTENT_URI, QUERY_COLUMNS, WHERE_ENABLED, null, null);
    }

    /**
     * Return an TimeSchedule object representing the schedule id in the database.
     * Returns null if no schedule exists.
     */
    public static TimeSchedule getTimeSchedule(ContentResolver contentResolver, long scheduleId) {
        Cursor cursor = contentResolver.query(ContentUris.withAppendedId(CONTENT_URI, scheduleId), QUERY_COLUMNS,
                null, null, null);
        TimeSchedule schedule = null;
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                schedule = new TimeSchedule(cursor);
            }
            cursor.close();
        }
        return schedule;
    }

    /**
     * A convenience method to set an time schedule in the Time Schedule
     * content provider.
     * @return Time when the time schedule will fire.
     */
    public static long setTimeSchedule(Context context, TimeSchedule timeSchedule) {
        ContentValues values = createContentValues(timeSchedule);
        ContentResolver resolver = context.getContentResolver();
        resolver.update(ContentUris.withAppendedId(CONTENT_URI, timeSchedule.id), values, null, null);
        setNextAction(context);
        return calculateTimeSchedule(timeSchedule);
    }

    /**
     * A convenience method to enable or disable an time schedule.
     *
     * @param id             corresponds to the _id column
     * @param enabled        corresponds to the ENABLED column
     */

    public static void enableTimeSchedule(final Context context, final long id, boolean enabled) {
        enableTimeScheduleInternal(context, id, enabled);
        setNextAction(context);
    }

    private static void enableTimeScheduleInternal(final Context context, final long id, boolean enabled) {
        enableTimeScheduleInternal(context, getTimeSchedule(context.getContentResolver(), id), enabled);
    }

    private static void enableTimeScheduleInternal(final Context context, final TimeSchedule schedule,
            boolean enabled) {
        if (schedule == null) {
            return;
        }
        ContentResolver resolver = context.getContentResolver();

        ContentValues values = new ContentValues(2);
        values.put(ENABLED, enabled ? 1 : 0);

        // If we are enabling the schedule, calculate schedule time since the time
        // value in TimeSchedule may be old.
        if (enabled) {
            long time = 0;
            if (!schedule.daysOfWeek.isRepeatSet()) {
                time = calculateTimeSchedule(schedule);
            }
            values.put(SCHEDULE_TIME, time);
        }
        resolver.update(ContentUris.withAppendedId(CONTENT_URI, schedule.id), values, null, null);
    }

    public static TimeSchedule calculateNextAction(final Context context) {
        TimeSchedule schedule = null;
        long minTime = Long.MAX_VALUE;
        long now = System.currentTimeMillis();
        Cursor cursor = getFilteredTimeSchedulesCursor(context.getContentResolver());
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                do {
                    TimeSchedule s = new TimeSchedule(cursor);

                    if (s.time == 0) {
                        s.time = calculateTimeSchedule(s);
                    } else if (s.time < now) {
                        Log.v("Disabling expired schedule set for " + Log.formatTime(s.time));
                        // Expired schedule, disable it and move along.
                        enableTimeScheduleInternal(context, s, false);
                        continue;
                    }
                    if (s.time < minTime) {
                        minTime = s.time;
                        schedule = s;
                    }
                } while (cursor.moveToNext());
            }
            cursor.close();
        }
        return schedule;
    }

    /**
     * Disables non-repeating schedules that have passed.  Called at
     * boot.
     */
    public static void disableExpiredSchedules(final Context context) {
        Cursor cur = getFilteredTimeSchedulesCursor(context.getContentResolver());
        long now = System.currentTimeMillis();

        if (cur.moveToFirst()) {
            do {
                TimeSchedule schedule = new TimeSchedule(cur);
                // A time of 0 means this schedule repeats. If the time is
                // non-zero, check if the time is before now.
                if (schedule.time != 0 && schedule.time < now) {
                    Log.v("Disabling expired schedule set for " + Log.formatTime(schedule.time));
                    enableTimeScheduleInternal(context, schedule, false);
                }
            } while (cur.moveToNext());
        }
        cur.close();
    }

    /**
     * Called at system startup, on time/timezone change, and whenever the user
     * changes schedule settings
     */
    public static void setNextAction(final Context context) {
        TimeSchedule schedule = calculateNextAction(context);
        if (schedule != null) {
            enableAction(context, schedule, schedule.time);
        } else {
            disableAction(context);
        }
    }

    /**
     * Sets action in AlarmManger.  This is what will
     * actually launch the action when the schedule triggers.
     *
     * @param schedule TimeSchedule.
     * @param atTimeInMillis milliseconds since epoch
     */
    @SuppressLint("NewApi")
    private static void enableAction(Context context, final TimeSchedule schedule, final long atTimeInMillis) {

        if (Log.LOGV) {
            Log.v("** setSchedule id " + schedule.id + " atTime " + atTimeInMillis);
        }

        Intent intent = new Intent(EXECUTE_SCHEDULE_ACTION);

        // XXX: This is a slight hack to avoid an exception in the remote
        // AlarmManagerService process. The AlarmManager adds extra data to
        // this Intent which causes it to inflate. Since the remote process
        // does not know about the TimeSchedule class, it throws a
        // ClassNotFoundException.
        //
        // To avoid this, we marshall the data ourselves and then parcel a plain
        // byte[] array. The ScheduleReceiver class knows to build the TimeSchedule
        // object from the byte[] array.
        Parcel out = Parcel.obtain();
        schedule.writeToParcel(out, 0);
        out.setDataPosition(0);
        intent.putExtra(INTENT_RAW_DATA, out.marshall());

        PendingIntent sender = PendingIntent.getBroadcast(context, schedule.hashCode(), intent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (Utils.isKitKatOrLater()) {
            am.setExact(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);
        } else {
            am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);
        }

        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(atTimeInMillis);
        String timeString = formatDayAndTime(context, c);
        saveNextAlarm(context, timeString);
    }

    /**
     * @param id Schedule ID.
     * 
     */
    static void disableAction(Context context) {
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        PendingIntent sender = PendingIntent.getBroadcast(context, 0, new Intent(EXECUTE_SCHEDULE_ACTION),
                PendingIntent.FLAG_CANCEL_CURRENT);
        am.cancel(sender);
        saveNextAlarm(context, "");
    }

    private static long calculateTimeSchedule(TimeSchedule schedule) {
        return calculateTimeSchedule(schedule.hour, schedule.minutes, schedule.daysOfWeek).getTimeInMillis();
    }

    static Calendar calculateTimeSchedule(int hour, int minute, TimeSchedule.DaysOfWeek daysOfWeek) {

        // start with now
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(System.currentTimeMillis());

        int nowHour = c.get(Calendar.HOUR_OF_DAY);
        int nowMinute = c.get(Calendar.MINUTE);

        // if schedule is behind current time, advance one day
        if (hour < nowHour || hour == nowHour && minute <= nowMinute) {
            c.add(Calendar.DAY_OF_YEAR, 1);
        }
        c.set(Calendar.HOUR_OF_DAY, hour);
        c.set(Calendar.MINUTE, minute);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);

        int addDays = daysOfWeek.getNextSchedule(c);
        if (addDays > 0)
            c.add(Calendar.DAY_OF_WEEK, addDays);
        return c;
    }

    public static String formatTime(final Context context, int hour, int minute,
            TimeSchedule.DaysOfWeek daysOfWeek) {
        Calendar c = calculateTimeSchedule(hour, minute, daysOfWeek);
        return formatTime(context, c);
    }

    public static String formatTime(final Context context, Calendar c) {
        String format = get24HourMode(context) ? M24 : M12;
        return (c == null) ? "" : (String) DateFormat.format(format, c);
    }

    private static String formatDayAndTime(final Context context, Calendar c) {
        String format = get24HourMode(context) ? DM24 : DM12;
        return (c == null) ? "" : (String) DateFormat.format(format, c);
    }

    static void saveNextAlarm(final Context context, String timeString) {
        android.provider.Settings.System.putString(context.getContentResolver(),
                Settings.System.NEXT_ALARM_FORMATTED, timeString);
    }

    /**
     * @return true if clock is set to 24-hour mode
     */
    public static boolean get24HourMode(final Context context) {
        return android.text.format.DateFormat.is24HourFormat(context);
    }

    public static final Parcelable.Creator<TimeSchedule> CREATOR = new Parcelable.Creator<TimeSchedule>() {

        @Override
        public TimeSchedule createFromParcel(Parcel p) {
            return new TimeSchedule(p);
        }

        @Override
        public TimeSchedule[] newArray(int size) {
            return new TimeSchedule[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel p, int flags) {
        p.writeLong(id);
        p.writeInt(enabled ? 1 : 0);
        p.writeInt(hour);
        p.writeInt(minutes);
        p.writeInt(daysOfWeek.getCoded());
        p.writeLong(time);
        p.writeLong(modeId);
    }

    public long id;
    public boolean enabled;
    public int hour;
    public int minutes;
    public DaysOfWeek daysOfWeek;
    public long time;
    public long modeId;

    public TimeSchedule(Cursor c) {
        id = c.getLong(ID_INDEX);
        enabled = c.getInt(ENABLED_INDEX) >= 1;
        hour = c.getInt(HOUR_INDEX);
        minutes = c.getInt(MINUTES_INDEX);
        daysOfWeek = new DaysOfWeek(c.getInt(DAYS_OF_WEEK_INDEX));
        time = c.getLong(SCHEDULE_TIME_INDEX);
        modeId = c.getLong(MODE_ID_INDEX);
    }

    public TimeSchedule(Parcel p) {
        id = p.readLong();
        enabled = p.readInt() == 1;
        hour = p.readInt();
        minutes = p.readInt();
        daysOfWeek = new DaysOfWeek(p.readInt());
        time = p.readLong();
        modeId = p.readInt();
    }

    // Creates a default schedule at the current time.
    public TimeSchedule() {
        id = -1;
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(System.currentTimeMillis());
        hour = c.get(Calendar.HOUR_OF_DAY);
        minutes = c.get(Calendar.MINUTE);
        daysOfWeek = new DaysOfWeek(0x7f);
        modeId = -1;
    }

    @Override
    public int hashCode() {
        return Long.valueOf(id).hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof TimeSchedule))
            return false;
        final TimeSchedule other = (TimeSchedule) o;
        return id == other.id;
    }

    @Override
    public String toString() {
        return "TimeSchedule{" + ", id=" + id + ", enabled=" + enabled + ", hour=" + hour + ", minutes=" + minutes
                + ", daysOfWeek=" + daysOfWeek + ", time=" + time + ", modeId=" + modeId + '}';
    }

    /*
     * Days of week code as a single int.
     * 0x00: no day
     * 0x01: Monday
     * 0x02: Tuesday
     * 0x04: Wednesday
     * 0x08: Thursday
     * 0x10: Friday
     * 0x20: Saturday
     * 0x40: Sunday
     */
    public static final class DaysOfWeek {

        private static int[] DAY_MAP = new int[] { Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY,
                Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY, Calendar.SUNDAY, };

        // Bitmask of all repeating days
        private int mDays;

        public DaysOfWeek(int days) {
            mDays = days;
        }

        public String toString(Context context, boolean showNever) {
            StringBuilder ret = new StringBuilder();

            // no days
            if (mDays == 0) {
                return showNever ? context.getText(R.string.never).toString() : "";
            }

            // every day
            if (mDays == 0x7f) {
                return context.getText(R.string.every_day).toString();
            }

            // count selected days
            int dayCount = 0, days = mDays;
            while (days > 0) {
                if ((days & 1) == 1)
                    dayCount++;
                days >>= 1;
            }

            // short or long form?
            DateFormatSymbols dfs = new DateFormatSymbols();
            String[] dayList = (dayCount > 1) ? dfs.getShortWeekdays() : dfs.getWeekdays();

            // selected days
            for (int i = 0; i < 7; i++) {
                if ((mDays & (1 << i)) != 0) {
                    ret.append(dayList[DAY_MAP[i]]);
                    dayCount -= 1;
                    if (dayCount > 0)
                        ret.append(context.getText(R.string.day_concat));
                }
            }
            return ret.toString();
        }

        private boolean isSet(int day) {
            return ((mDays & (1 << day)) > 0);
        }

        public void set(int day, boolean set) {
            if (set) {
                mDays |= (1 << day);
            } else {
                mDays &= ~(1 << day);
            }
        }

        public void set(DaysOfWeek dow) {
            mDays = dow.mDays;
        }

        public int getCoded() {
            return mDays;
        }

        // Returns days of week encoded in an array of booleans.
        public boolean[] getBooleanArray() {
            boolean[] ret = new boolean[7];
            for (int i = 0; i < 7; i++) {
                ret[i] = isSet(i);
            }
            return ret;
        }

        public boolean isRepeatSet() {
            return mDays != 0;
        }

        /**
         * returns number of days from today until next schedule
         * @param c must be set to today
         */
        public int getNextSchedule(Calendar c) {
            if (mDays == 0) {
                return -1;
            }

            int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7;

            int day = 0;
            int dayCount = 0;
            for (; dayCount < 7; dayCount++) {
                day = (today + dayCount) % 7;
                if (isSet(day)) {
                    break;
                }
            }
            return dayCount;
        }
    }

}