com.albedinsky.android.support.intent.CalendarIntent.java Source code

Java tutorial

Introduction

Here is the source code for com.albedinsky.android.support.intent.CalendarIntent.java

Source

/*
 * =================================================================================================
 *                             Copyright (C) 2014 Martin Albedinsky
 * =================================================================================================
 *         Licensed under the Apache License, Version 2.0 or later (further "License" only).
 * -------------------------------------------------------------------------------------------------
 * You may use this file only in compliance with the License. More details and copy of this License 
 * you may obtain at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * You can redistribute, modify or publish any part of the code written within this file but as it 
 * is described in the License, the software distributed under the License is distributed on an 
 * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND.
 * 
 * See the License for the specific language governing permissions and limitations under the License.
 * =================================================================================================
 */
package com.albedinsky.android.support.intent;

import android.app.Activity;
import android.content.ContentUris;
import android.content.Intent;
import android.net.Uri;
import android.provider.CalendarContract;
import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A {@link BaseIntent} builder implementation providing API for building of intents targeting
 * a <b>calendar</b> related applications.
 * <p>
 * Whether a started calendar intent should insert a new event or edit the existing one can be
 * specified via {@link #type(int)} and supplying one of {@link #TYPE_VIEW}, {@link #TYPE_INSERT_EVENT},
 * {@link #TYPE_EDIT_EVENT}, {@link #TYPE_VIEW_EVENT}.
 *
 * <h3>Intent type</h3>
 * <ul>
 * <li>
 * {@link #TYPE_VIEW}
 * <p>
 * Use this type when you want to open only calendar at the current date for preview. If you want to
 * open calendar at different date, you can specify your desired date via {@link #beginTime(long)}.
 * </li>
 * <li>
 * {@link #TYPE_VIEW_EVENT}
 * <p>
 * Use this type when you want to open an existing calendar event for preview. This type requires an
 * id of the desired event to be specified via {@link #eventId(long)} otherwise calendar intent cannot
 * be build properly and {@link #build()} will return {@code null}.
 * </li>
 * <li>
 * {@link #TYPE_EDIT_EVENT}
 * <p>
 * Use this type when you want to open an existing calendar event for editing. This type requires an
 * id of the desired event like described for {@link #TYPE_VIEW_EVENT} above.
 * </li>
 * <li>
 * {@link #TYPE_INSERT_EVENT}
 * <p>
 * Use this type when you want to create a new calendar event. This type requires a begin time to be
 * specified via {@link #beginTime(long)} and an end time via {@link #endTime(long)}. Following data
 * are relatively optional but for 'good' calendar event should be always set: {@link #title(CharSequence)},
 * {@link #description(CharSequence)}, {@link #location(CharSequence)}, {@link #availability(int)}.
 * </li>
 * </ul>
 *
 * @author Martin Albedinsky
 */
public class CalendarIntent extends BaseIntent<CalendarIntent> {

    /**
     * Interface ===================================================================================
     */

    /**
     * Constants ===================================================================================
     */

    /**
     * Log TAG.
     */
    // private static final String TAG = "CalendarIntent";

    /**
     * Defines an annotation for determining set of allowed flags for {@link #type(int)} method.
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ TYPE_VIEW, TYPE_INSERT_EVENT, TYPE_EDIT_EVENT, TYPE_VIEW_EVENT })
    public @interface Type {
    }

    /**
     * Flag to identify CalendarIntent as intent to open calendar. Can be passed only to {@link #type(int)}.
     */
    public static final int TYPE_VIEW = 0x01;

    /**
     * Flag to identify CalendarIntent as intent to create new calendar event. Can be passed only to
     * {@link #type(int)}.
     */
    public static final int TYPE_INSERT_EVENT = 0x02;

    /**
     * Flag to identify CalendarIntent as intent to edit existing calendar event. Can be passed only
     * to {@link #type(int)}.
     */
    public static final int TYPE_EDIT_EVENT = 0x03;

    /**
     * Flag to identify CalendarIntent as intent to view existing calendar event. Can be passed only
     * to {@link #type(int)}.
     */
    public static final int TYPE_VIEW_EVENT = 0x04;

    /**
     * Defines an annotation for determining set of allowed flags for {@link #availability(int)} method.
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ AVAILABILITY_BUSY, AVAILABILITY_FREE, AVAILABILITY_TENTATIVE })
    public @interface Availability {
    }

    /**
     * Flag to identify BUSY availability for the newly creating calendar event.
     * See {@link CalendarContract.Events#AVAILABILITY_BUSY} for additional info.
     */
    public static final int AVAILABILITY_BUSY = CalendarContract.Events.AVAILABILITY_BUSY;

    /**
     * Flag to identify FREE availability for the newly creating calendar event.
     * See {@link CalendarContract.Events#AVAILABILITY_FREE} for additional info.
     */
    public static final int AVAILABILITY_FREE = CalendarContract.Events.AVAILABILITY_FREE;

    /**
     * Flag to identify TENTATIVE availability for the newly creating calendar event.
     * See {@link CalendarContract.Events#AVAILABILITY_TENTATIVE} for additional info.
     */
    public static final int AVAILABILITY_TENTATIVE = 0x02;

    /**
     * Static members ==============================================================================
     */

    /**
     * Members =====================================================================================
     */

    /**
     * Type of intent creating by this builder.
     */
    private int mType = TYPE_INSERT_EVENT;

    /**
     * Id of newly creating or already existing calendar event.
     */
    private long mEventId = -1;

    /**
     * Time for newly creating calendar event.
     */
    private long mBeginTime, mEndTime;

    /**
     * Title for newly creating calendar event.
     */
    private CharSequence mTitle;

    /**
     * Description for newly creating calendar event.
     */
    private CharSequence mDescription;

    /**
     * Location for newly creating calendar event.
     */
    private CharSequence mLocation;

    /**
     * Availability for newly creating calendar event.
     */
    private int mAvailability = AVAILABILITY_BUSY;

    /**
     * Constructors ================================================================================
     */

    /**
     * Creates a new instance of CalendarIntent for the given <var>activity</var> context with
     * the <b>begin</b> time set to the current one and <b>end</b> time set to +1 after the <b>begin</b>
     * time. See {@link com.albedinsky.android.support.intent.BaseIntent#BaseIntent(Activity)}
     * for additional info.
     */
    public CalendarIntent(@NonNull Activity activity) {
        super(activity);
        this.init();
    }

    /**
     * Creates a new instance of CalendarIntent for the given <var>fragment</var> context with
     * the <b>begin</b> time set to the current one and <b>end</b> time set to +1 after the <b>begin</b>
     * time. See {@link com.albedinsky.android.support.intent.BaseIntent#BaseIntent(android.support.v4.app.Fragment)}
     * for additional info.
     */
    public CalendarIntent(@NonNull Fragment fragment) {
        super(fragment);
        this.init();
    }

    /**
     * Methods =====================================================================================
     */

    /**
     * Initializes default values for this instance of calendar intent builder.
     */
    private void init() {
        this.mBeginTime = currentTime();
        this.mEndTime = mBeginTime + 1;
    }

    /**
     * Wrapped {@link System#currentTimeMillis()}.
     */
    public static long currentTime() {
        return System.currentTimeMillis();
    }

    /**
     * Sets a type determining whether to create, view or edit an event in the calendar.
     * <p>
     * Default value: <b>{@link #TYPE_INSERT_EVENT}</b>
     *
     * @param type One of {@link #TYPE_VIEW}, {@link #TYPE_INSERT_EVENT}, {@link #TYPE_EDIT_EVENT},
     *             {@link #TYPE_VIEW_EVENT}.
     * @return This intent builder to allow methods chaining.
     * @see #type()
     */
    public CalendarIntent type(@Type int type) {
        switch (type) {
        case TYPE_VIEW:
        case TYPE_INSERT_EVENT:
        case TYPE_EDIT_EVENT:
        case TYPE_VIEW_EVENT:
            this.mType = type;
        }
        return this;
    }

    /**
     * Returns the type of the intent that will be created by this intent builder.
     * <p>
     * Default value: <b>{@link #TYPE_INSERT_EVENT}</b>
     *
     * @return One of {@link #TYPE_VIEW}, {@link #TYPE_INSERT_EVENT}, {@link #TYPE_EDIT_EVENT},
     * {@link #TYPE_VIEW_EVENT}.
     * @see #type(int)
     */
    @Type
    public int type() {
        return mType;
    }

    /**
     * Sets an id of the calendar event to be edited or viewed. This id is only used in case of
     * intent type {@link #TYPE_EDIT_EVENT} or {@link #TYPE_VIEW_EVENT}.
     * <p>
     * Default value: <b>{@code -1}</b>
     *
     * @param eventId Id of the desired calendar event (to edit or view).
     * @return This intent builder to allow methods chaining.
     * @see #type(int)
     */
    public CalendarIntent eventId(@IntRange(from = 1, to = Long.MAX_VALUE) long eventId) {
        this.mEventId = eventId;
        return this;
    }

    /**
     * Returns the id of the event to edit or view within calendar.
     *
     * @return Event id.
     */
    public long eventId() {
        return mEventId;
    }

    /**
     * Sets a time determining where to open the calendar for view. This time is only used in case
     * of intent type {@link #TYPE_VIEW}.
     * <p>
     * <b>Note</b>, that the default time is set to the current one ({@link #currentTime()}).
     *
     * @param time The desired time where to open the calendar.
     * @return This intent builder to allow methods chaining.
     * @see #time()
     */
    public CalendarIntent time(@IntRange(from = 0, to = Long.MAX_VALUE) long time) {
        this.mBeginTime = time;
        return this;
    }

    /**
     * Returns the time determining where to open the calendar for view.
     * <p>
     * Default value: <b>{@link #currentTime()}</b>
     *
     * @return Time for calendar in milliseconds.
     * @see #time(long)
     */
    public long time() {
        return mBeginTime;
    }

    /**
     * Sets a begin time for the calendar event to be newly created. This time is only used in case
     * of intent type {@link #TYPE_INSERT_EVENT} or {@link #TYPE_VIEW}.
     * <p>
     * <b>Note</b>, that the default begin time is set to the current one ({@link #currentTime()}).
     *
     * @param beginTime The desired begin time for the new event in milliseconds.
     * @return This intent builder to allow methods chaining.
     * @see #endTime(long)
     * @see #type(int)
     * @see #beginTime()
     */
    public CalendarIntent beginTime(@IntRange(from = 0, to = Long.MAX_VALUE) long beginTime) {
        this.mBeginTime = beginTime;
        return this;
    }

    /**
     * Returns the begin time for the new calendar event.
     * <p>
     * Default value: <b>{@link #currentTime()}</b>
     *
     * @return Begin time for calendar event in milliseconds.
     * @see #beginTime(long)
     * @see #endTime(long)
     */
    public long beginTime() {
        return mBeginTime;
    }

    /**
     * Sets an end time for the calendar event to be newly created. This time is only used in case
     * of intent type {@link #TYPE_INSERT_EVENT}.
     * <p>
     * <b>Note</b>, that the default end time is set to {@code +1} of the current begin time
     * ({@link #beginTime()}).
     *
     * @param endTime The desired end time for the new event in milliseconds.
     * @return This intent builder to allow methods chaining.
     * @see #beginTime(long)
     * @see #type(int)
     * @see #endTime()
     */
    public CalendarIntent endTime(@IntRange(from = 0, to = Long.MAX_VALUE) long endTime) {
        this.mEndTime = endTime;
        return this;
    }

    /**
     * Returns the end time for the new calendar event.
     * <p>
     * Default value: <b>{@link #currentTime()} + 1</b>
     *
     * @return End time for calendar event in milliseconds.
     * @see #endTime(long)
     * @see #beginTime(long)
     */
    public long endTime() {
        return mEndTime;
    }

    /**
     * Same as {@link #title(CharSequence)} for resource id.
     *
     * @param resId Resource id of the desired title text.
     * @see #title()
     */
    public CalendarIntent title(@StringRes int resId) {
        return title(obtainText(resId));
    }

    /**
     * Sets a title for the calendar event to be newly created. This text is used only in case of
     * intent type {@link #TYPE_INSERT_EVENT} or {@link #TYPE_EDIT_EVENT}.
     *
     * @param title The desired title text.
     * @return This intent builder to allow methods chaining.
     * @see #title(int)
     * @see #title()
     */
    public CalendarIntent title(@Nullable CharSequence title) {
        this.mTitle = title;
        return this;
    }

    /**
     * Returns the title for the new calendar event.
     *
     * @return Title text for calendar event.
     * @see #title(int)
     * @see #title(CharSequence)
     */
    @NonNull
    public CharSequence title() {
        return mTitle != null ? mTitle : "";
    }

    /**
     * Same as {@link #description(CharSequence)} for resource id.
     *
     * @param resId Resource id of the desired description text.
     * @see #description()
     */
    public CalendarIntent description(@StringRes int resId) {
        return description(obtainString(resId));
    }

    /**
     * Sets a description for the calendar event to be newly created. This text is only used in case
     * of intent type {@link #TYPE_INSERT_EVENT}.
     *
     * @param description The desired description text.
     * @return This intent builder to allow methods chaining.
     * @see #description(int)
     * @see #description()
     */
    public CalendarIntent description(@Nullable CharSequence description) {
        this.mDescription = description;
        return this;
    }

    /**
     * Returns the description for the new calendar event.
     *
     * @return Description text for calendar event.
     * @see #description(int)
     * @see #description(CharSequence)
     */
    @NonNull
    public CharSequence description() {
        return mDescription != null ? mDescription : "";
    }

    /**
     * Same as {@link #location(CharSequence)} for resource id.
     *
     * @param resId Resource id of the desired location text.
     */
    public CalendarIntent location(@StringRes int resId) {
        return location(obtainText(resId));
    }

    /**
     * Sets a location for the calendar event to be newly created. This text is only used in case of
     * intent type {@link #TYPE_INSERT_EVENT}.
     *
     * @param location The desired location text.
     * @return This intent builder to allow methods chaining.
     * @see #location(int)
     */
    public CalendarIntent location(@Nullable CharSequence location) {
        this.mLocation = location;
        return this;
    }

    /**
     * Returns the location for the new calendar event.
     *
     * @return Location text for calendar event.
     */
    @NonNull
    public CharSequence location() {
        return mLocation != null ? mLocation : "";
    }

    /**
     * Sets an availability flag for the calendar event to be newly created. This flag is used only
     * in case of intent type {@link #TYPE_INSERT_EVENT}.
     * <p>
     * Default value: <b>{@link #AVAILABILITY_BUSY}</b>
     *
     * @param availability One of {@link #AVAILABILITY_BUSY}, {@link #AVAILABILITY_FREE} or {@link #AVAILABILITY_TENTATIVE}.
     * @return This intent builder to allow methods chaining.
     */
    public CalendarIntent availability(@Availability int availability) {
        switch (availability) {
        case AVAILABILITY_BUSY:
        case AVAILABILITY_FREE:
        case AVAILABILITY_TENTATIVE:
            this.mAvailability = availability;
        }
        return this;
    }

    /**
     * Returns the availability flag for the new calendar event.
     * <p>
     * Default value: <b>{@link #AVAILABILITY_BUSY}</b>
     *
     * @return Availability flag. One of {@link #AVAILABILITY_BUSY}, {@link #AVAILABILITY_FREE} or
     * {@link #AVAILABILITY_TENTATIVE}.
     */
    @Availability
    public int availability() {
        return mAvailability;
    }

    /**
     */
    @Override
    protected void ensureCanBuildOrThrow() {
        super.ensureCanBuildOrThrow();
        switch (mType) {
        case TYPE_VIEW:
            checkTimeOrThrow(mBeginTime, "Specified invalid time(" + mBeginTime
                    + ") where to open calendar for view. " + "Must be none-negative time value.");
            break;
        case TYPE_INSERT_EVENT:
            checkTimeOrThrow(mBeginTime,
                    "Specified invalid begin time(" + mBeginTime + "). " + "Must be none-negative time value.");
            checkTimeOrThrow(mEndTime,
                    "Specified invalid end time(" + mEndTime + "). " + "Must be none-negative time value.");
            if (mEndTime <= mBeginTime) {
                throw cannotBuildIntentException("Specified end time(" + mEndTime + ") is before/at begin time("
                        + mBeginTime + "). " + "Must be greater than the begin time.");
            }
            break;
        case TYPE_EDIT_EVENT:
        case TYPE_VIEW_EVENT:
            checkEventIdOrThrow();
            break;
        }
    }

    /**
     * Checks the given value of time if it is valid (none-negative).
     *
     * @param time             The desired time in milliseconds to check.
     * @param exceptionMessage The desired message for the exception if the checked time is invalid.
     * @throws AndroidRuntimeException If the specified time is not valid.
     */
    private void checkTimeOrThrow(long time, String exceptionMessage) {
        if (time < 0)
            throw cannotBuildIntentException(exceptionMessage);
    }

    /**
     * Checks the calendar event id passed to this builder.
     *
     * @throws AndroidRuntimeException If the event id is not valid.
     */
    private void checkEventIdOrThrow() {
        if (mEventId <= 0)
            throw cannotBuildIntentException("Specified invalid event id(" + mEventId + ").");
    }

    /**
     */
    @NonNull
    @Override
    @SuppressWarnings("ConstantConditions")
    protected Intent onBuild() {
        switch (mType) {
        case TYPE_VIEW:
            final Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
            builder.appendPath("time");
            ContentUris.appendId(builder, mBeginTime);
            return new Intent(Intent.ACTION_VIEW).setData(builder.build());
        case TYPE_INSERT_EVENT:
            return new Intent(Intent.ACTION_INSERT).setData(CalendarContract.Events.CONTENT_URI)
                    .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, mBeginTime)
                    .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, mEndTime)
                    .putExtra(CalendarContract.Events.TITLE, mTitle)
                    .putExtra(CalendarContract.Events.DESCRIPTION, mDescription)
                    .putExtra(CalendarContract.Events.EVENT_LOCATION, mLocation)
                    .putExtra(CalendarContract.Events.AVAILABILITY, mAvailability);
        case TYPE_EDIT_EVENT:
            final Intent intent = new Intent(Intent.ACTION_EDIT);
            intent.setData(ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, mEventId));
            if (!TextUtils.isEmpty(mTitle)) {
                intent.putExtra(CalendarContract.Events.TITLE, mTitle);
            }
            return intent;
        default:
            return new Intent(Intent.ACTION_VIEW)
                    .setData(ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, mEventId));
        }
    }

    /**
     * Inner classes ===============================================================================
     */
}