<|start_filename|>app/src/main/java/com/dinodevs/pacecalendarwidget/APtouch.java<|end_filename|>
package com.dinodevs.pacecalendarwidget;

import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.util.Calendar;

public class APtouch {

    private int LONG_CLICK_DURATION = 1000;   // long click time
    private int LONG_CLICK_MOVEMENT_THRESHOLD = 10;   // long click time
    private float MIN_SWIPE_LENGTH = 80;     // minimum length of a swipe
    private long MAX_SWIPE_TIME = 250;       // maximum time of a swipe
    private long MAX_DOUBLE_CLICK_TIME = 300;// maximum time of a double click

    private View.OnTouchListener listener;

    private Handler handler;
    private boolean handler_active;
    private Runnable handler_runnable;

    private long last_click_time;
    private long start_time;
    private float start_position_x;
    private float start_position_y;
    private boolean moved;

    APtouch () {

        // Double click timer
        this.last_click_time = 0;

        // Long Click handler
        this.handler = new Handler();
        this.handler_runnable = new Runnable() {
            @Override
            public void run(){
                onLongClick();
            }
        };

        // Create listener
        this.listener = new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                // Detect action
                switch (motionEvent.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        return action_down(motionEvent);

                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:
                        return action_up(motionEvent);

                    case MotionEvent.ACTION_MOVE:
                        return action_move(motionEvent);
                }
                return true;
            }
        };
    }

    private String TAG = "PaceCalendarWidget";

    // Handle actions
    private boolean action_down(MotionEvent motionEvent){
        // Save action start info
        this.start_time = Calendar.getInstance().getTimeInMillis();
        this.start_position_x = motionEvent.getX();
        this.start_position_y = motionEvent.getY();
        this.moved = false;

        // Log.d(this.TAG, "[Action Down] {x:" + this.start_position_x + ", y:" + this.start_position_y + "}");

        // Trigger long click in x ms
        this.handler_active = true;
        this.handler.postDelayed(this.handler_runnable, this.LONG_CLICK_DURATION);

        return true;
    }
    private boolean action_move(MotionEvent motionEvent){
        // Find dx and dy
        float dx = Math.abs(this.start_position_x - motionEvent.getX());
        float dy = Math.abs(this.start_position_y - motionEvent.getY());
        this.moved = true;

        // Log.d(this.TAG, "[Action Move] {dx:" + dx + ", dy:" + dy + "}");

        // Disable long click
        if (this.handler_active && (dx > this.LONG_CLICK_MOVEMENT_THRESHOLD || dy > this.LONG_CLICK_MOVEMENT_THRESHOLD)) {
            this.handler_active = false;
            this.handler.removeCallbacks(this.handler_runnable);
        }

        return true;
    }
    private boolean action_up(MotionEvent motionEvent){
        // Disable long click
        if (this.handler_active) {
            this.handler_active = false;
            this.handler.removeCallbacks(this.handler_runnable);
        }

        long now = Calendar.getInstance().getTimeInMillis();

        // If it was a click
        if (!this.moved) {
            if (this.last_click_time > 0 && now - this.last_click_time < this.MAX_DOUBLE_CLICK_TIME) {
                this.last_click_time = 0;
                return onDoubleClick();
            }
            else {
                this.last_click_time = now;
                return onClick();
            }
        }

        this.last_click_time = 0;

        // Check if haven't met time limit for a swipe
        if (now - this.start_time > this.MAX_SWIPE_TIME) {
            // Log.d(this.TAG, "[Action Up] Not time long enough to be a swipe.");
            return true;
        }

        // Find dx and dy
        float end_position_x = motionEvent.getX();
        float end_position_y = motionEvent.getY();
        float dx = Math.abs(this.start_position_x - end_position_x);
        float dy = Math.abs(this.start_position_y - end_position_y);

        // Check if haven't met distance limit
        if (dx < this.MIN_SWIPE_LENGTH && dy < this.MIN_SWIPE_LENGTH) {
            // Log.d(this.TAG, "[Action Up] Not distance long enough to be a swipe.");
            return true;
        }

        // Detect direction
        if (dx > dy) {
            if (this.start_position_x < end_position_x) {
                // Swipe right
                // Log.d(this.TAG, "[Action Up] Swipe Right.");
                return onSwipeRight();
            }
            else {
                // Swipe left
                // Log.d(this.TAG, "[Action Up] Swipe Left.");
                return onSwipeLeft();
            }
        }
        else {
            if (this.start_position_y < end_position_y){
                // Swipe down
                // Log.d(this.TAG, "[Action Up] Swipe Down.");
                return onSwipeDown();
            }
            else {
                // Swipe up
                // Log.d(this.TAG, "[Action Up] Swipe Up.");
                return onSwipeUp();
            }
        }
    }


    // Function to attach listener
    public View.OnTouchListener listen() {
        return this.listener;
    }

    // Events
    public boolean onSwipeUp() {return true;}
    public boolean onSwipeDown() {return true;}
    public boolean onSwipeLeft() {return true;}
    public boolean onSwipeRight() {return true;}
    public boolean onClick() {return true;}
    public boolean onDoubleClick() {return this.onClick();}
    public boolean onLongClick() {return true;}
}


<|start_filename|>app/src/main/java/com/dinodevs/pacecalendarwidget/widgetTimeline.java<|end_filename|>
package com.dinodevs.pacecalendarwidget;

        import android.annotation.SuppressLint;
        import android.content.Context;
        import android.content.Intent;
        import android.content.pm.PackageInfo;
        import android.content.pm.PackageManager;
        import android.graphics.Bitmap;
        import android.graphics.drawable.BitmapDrawable;
        import android.os.Bundle;
        import android.provider.Settings;
        import android.util.Log;
        import android.view.Gravity;
        import android.view.LayoutInflater;
        import android.view.View;
        import android.widget.ListAdapter;
        import android.widget.ListView;
        import android.widget.SimpleAdapter;
        import android.widget.TextView;
        import android.widget.Toast;

        import org.json.JSONArray;
        import org.json.JSONException;
        import org.json.JSONObject;

        import java.text.SimpleDateFormat;
        import java.util.ArrayList;
        import java.util.Calendar;
        import java.util.HashMap;
        import java.util.Locale;

        import clc.sliteplugin.flowboard.AbstractPlugin;
        import clc.sliteplugin.flowboard.ISpringBoardHostStub;

public class widgetTimeline extends AbstractPlugin {

    // Tag for logging purposes.
    private final static String TAG = "TimelineWidget";

    private final static int CALENDAR_DATA_INDEX_TITLE = 0;
    private final static int CALENDAR_DATA_INDEX_DESCRIPTION = 1;
    private final static int CALENDAR_DATA_INDEX_START = 2;
    private final static int CALENDAR_DATA_INDEX_END = 3;
    private final static int CALENDAR_DATA_INDEX_LOCATION = 4;
    private final static int CALENDAR_DATA_INDEX_ACCOUNT = 5;
    private final static int CALENDAR_EXTENDED_DATA_INDEX = 6;

    private final static String CALENDAR_DATA_PARAM_EXTENDED_DATA_ALL_DAY = "all_day";

    // Version
    private String version = "n/a";

    // Activity variables
    private boolean isActive = false;
    private Context mContext;
    private View mView;

    private ListView lv;
    private ArrayList<HashMap<String, String>> eventsList;

    private long next_event;
    private String calendarEvents;
    private String header_pattern;
    private String time_pattern;

    // Constants
    private static final String HEADER_PATTERN_12H = "hh:mm a\nEEEE, d MMMM";
    private static final String HEADER_PATTERN_24H = "HH:mm\nEEEE, d MMMM";
    private static final String ELEMENT_PATTERN = "EEEE, d MMMM";
    private static final String TIME_PATTERN_12H = "hh:mm a";
    private static final String TIME_PATTERN_24H = "HH:mm";
    private static final String DATE_PATTERN = "dd/MM/yyyy";


    // Set up the widget's layout
    @Override
    public View getView(Context paramContext) {
        // Save Activity variables
        this.mContext = paramContext;
        this.mView = LayoutInflater.from(paramContext).inflate(R.layout.widget_timeline, null);

        // Initialize variables
        Log.d(widgetTimeline.TAG, "Starting widget...");
        this.init();

        // Attach event listeners
        Log.d(widgetTimeline.TAG, "Attaching listeners...");
        this.initListeners();

        // Finish
        Log.d(widgetTimeline.TAG, "Done...");
        return this.mView;
    }

    // Initialize widget
    private void init() {
        // Get widget version number
        try {
            PackageInfo pInfo = this.mContext.getPackageManager().getPackageInfo(this.mContext.getPackageName(), 0);
            this.version = pInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        // Show Time/Date
        refresh_time();

        // Calendar Events Data
        eventsList = new ArrayList<>();
        lv = (ListView) this.mView.findViewById(R.id.list);

        loadCalendarEvents();
    }

    // Attach listeners
    @SuppressLint("ClickableViewAccessibility")
    private void initListeners(){
        // About button event
        TextView time = this.mView.findViewById(R.id.time);
        time.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                refresh_time();
                widgetTimeline.this.toast("Timeline Widget v" + widgetTimeline.this.version + " by GreatApo");
            }
        });
        // Refresh events
        time.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                refresh_time();
                loadCalendarEvents();
                widgetTimeline.this.toast("Events were refreshed");
                return true;
            }
        });
        // Scroll to top
        TextView top = this.mView.findViewById(R.id.backToTop);
        top.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ListView list = widgetTimeline.this.mView.findViewById(R.id.list);
                list.setSelectionAfterHeaderView();
                //widgetTimeline.this.toast("▲ Top");
            }
        });
    }

    private void refresh_time(){
        is24h();
        TextView time = this.mView.findViewById(R.id.time);
        time.setText( dateToString(java.util.Calendar.getInstance(),header_pattern) );
    }

    boolean is24h;
    private void is24h(){
        is24h = Settings.System.getString(mContext.getContentResolver(), "time_12_24").equals("24");
        Log.d(TAG, "Timeline init is24h: " + is24h);

        if (is24h) {
            header_pattern = HEADER_PATTERN_24H;
            time_pattern = TIME_PATTERN_24H;
        } else {
            header_pattern = HEADER_PATTERN_12H;
            time_pattern = TIME_PATTERN_12H;
        }
    }

    private void loadCalendarEvents() {
        eventsList = new ArrayList<>();
        next_event = 0;
        is24h();

        // Load data
        calendarEvents = Settings.System.getString(mContext.getContentResolver(), "CustomCalendarData");

        if(calendarEvents !=null && !calendarEvents.isEmpty() ) {
            try {
                // Check if correct form of JSON
                JSONObject json_data = new JSONObject(calendarEvents);

                // If there are events
                if (json_data.has("events")) {
                    int event_number = json_data.getJSONArray("events").length();

                    java.util.Calendar calendar = java.util.Calendar.getInstance();
                    calendar.add(java.util.Calendar.MINUTE, -10); // Show only future events + 10 minutes old
                    long current_time = calendar.getTimeInMillis();
                    String current_loop_date = "";

                    // Get data
                    for (int i = 0; i < event_number; i++) {
                        JSONArray data = json_data.getJSONArray("events").getJSONArray(i);
                        HashMap<String, String> event = new HashMap<>();

                        // adding each child node to HashMap key => value
                        event.put("title", data.getString(CALENDAR_DATA_INDEX_TITLE));
                        //event.put("description", data.getString(1));
                        //event.put("start", data.getString(2));
                        //event.put("end", data.getString(3));
                        //event.put("location", data.getString(4));
                        //event.put("account", data.getString(5));

                        String start = "N/A";
                        String end = "";
                        String location = "";

                        long endTimeInMillis = -1;

                        // No end
                        String end_item = data.getString(CALENDAR_DATA_INDEX_END);
                        if (!end_item.equals("") && !end_item.equals("null")) {
                            endTimeInMillis = Long.parseLong(end_item);

                            if (current_time > endTimeInMillis) {
                                // Event expired, go to next
                                continue;
                            }

                            calendar.setTimeInMillis(endTimeInMillis);
                            end = " - " + dateToString(calendar, time_pattern);
                        }

                        String start_item = data.getString(CALENDAR_DATA_INDEX_START);
                        if (!start_item.equals("") && !start_item.equals("null")) {
                            calendar.setTimeInMillis(Long.parseLong(start_item));

                            if (endTimeInMillis == -1 && current_time > calendar.getTimeInMillis()) {
                                // Event have no end time an begin time is expired, go to next
                                continue;
                            }
                            if (next_event == 0) // Hence this is the next event
                                next_event = calendar.getTimeInMillis();

                            start = dateToString(calendar, time_pattern);

                            // Insert day separator, or not :P
                            if (!current_loop_date.equals(dateToString(calendar, ELEMENT_PATTERN))) {
                                current_loop_date = dateToString(calendar, ELEMENT_PATTERN);
                                HashMap<String, String> date_elem = new HashMap<>();
                                date_elem.put("title", "");
                                date_elem.put("subtitle", (current_loop_date.equals(dateToString(java.util.Calendar.getInstance(), ELEMENT_PATTERN)))?mContext.getResources().getString(R.string.today):current_loop_date);
                                date_elem.put("dot", "");
                                eventsList.add(date_elem);
                            }
                        } else {
                            // Event has no date, go to next
                            continue;
                        }

                        // All day events
                        boolean all_day = false;
                        JSONObject extended_data = (JSONObject) data.opt(CALENDAR_EXTENDED_DATA_INDEX);

                        if (extended_data != null) {
                            all_day = "1".equals(
                                    extended_data.get(CALENDAR_DATA_PARAM_EXTENDED_DATA_ALL_DAY));
                        } else {
                            // Old protocol, fallback to old handler.

                            if ((start.startsWith("00") || start.startsWith("12")) && end_item.equals("null")) {
                                all_day = true;
                            }
                        }
                        if (all_day) {
                            start = mContext.getResources().getString(R.string.all_day);
                            end = "";
                        }

                        // Location
                        String location_item = data.getString(CALENDAR_DATA_INDEX_LOCATION);
                        if (!location_item.equals("") && !location_item.equals("null")) {
                            location = "\n@ " + location_item;
                        }

                        event.put("subtitle", start + end + location);
                        event.put("dot", mContext.getResources().getString(R.string.bull));
                        // adding events to events list
                        eventsList.add(event);
                    }
                }
            } catch (JSONException e) {
                //default
                HashMap<String, String> event = new HashMap<>();
                event.put("title", mContext.getResources().getString(R.string.no_events));
                //event.put("description", "-");
                //event.put("start", "-");
                //event.put("end", "-");
                //event.put("location", "-");
                //event.put("account", "-");
                event.put("subtitle", "-");
                event.put("dot", "");
                eventsList.add(event);
            }
        }

        if(eventsList.isEmpty()){
            HashMap<String, String> elem = new HashMap<>();
            elem.put("title", "\n"+mContext.getResources().getString(R.string.no_events));
            elem.put("subtitle", mContext.getResources().getString(R.string.no_events_description));
            elem.put("dot", "" );
            eventsList.add(elem);
        }

        ListAdapter adapter = new SimpleAdapter(mContext, eventsList, R.layout.list_item, new String[]{"title", "subtitle", "dot"}, new int[]{R.id.title, R.id.description, R.id.dot});
        lv.setAdapter(adapter);
    }


    // Toast wrapper
    private void toast (String message) {
        Toast toast = Toast.makeText(this.mContext, message, Toast.LENGTH_SHORT);
        TextView v = (TextView) toast.getView().findViewById(android.R.id.message);
        if( v != null) v.setGravity(Gravity.CENTER);
        toast.show();
    }

    // Convert a date to format
    private String dateToString (java.util.Calendar date) {
        return (new SimpleDateFormat(DATE_PATTERN, Locale.US)).format(date.getTime());
    }
    private String dateToString (java.util.Calendar date, String pattern) {
        return (new SimpleDateFormat(pattern, Locale.US)).format(date.getTime());
    }


    /*
     * Widget active/deactivate state management
     */

    // On widget show
    private void onShow() {
        // If view loaded (and was inactive)
        if (this.mView != null && !this.isActive) {
            // If an event expired OR new events OR changed 12/24h
            if ( next_event+10*1000 < Calendar.getInstance().getTimeInMillis() || !calendarEvents.equals(Settings.System.getString(mContext.getContentResolver(), "CustomCalendarData")) || is24h != Settings.System.getString(mContext.getContentResolver(), "time_12_24").equals("24") ) {
                // Refresh timeline
                loadCalendarEvents();
            }
            refresh_time();
        }

        // Save state
        this.isActive = true;
    }

    // On widget hide
    private void onHide() {
        // Save state
        this.isActive = false;
    }


    // Events for widget hide
    @Override
    public void onInactive(Bundle paramBundle) {
        super.onInactive(paramBundle);
        this.onHide();
    }
    @Override
    public void onPause() {
        super.onPause();
        this.onHide();
    }
    @Override
    public void onStop() {
        super.onStop();
        this.onHide();
    }

    // Events for widget show
    @Override
    public void onActive(Bundle paramBundle) {
        super.onActive(paramBundle);
        this.onShow();
    }
    @Override
    public void onResume() {
        super.onResume();
        this.onShow();
    }



    /*
     * Below where are unchanged functions that the widget should have
     */

    // Return the icon for this page, used when the page is disabled in the app list. In this case, the launcher icon is used
    @Override
    public Bitmap getWidgetIcon(Context paramContext) {
        return ((BitmapDrawable) this.mContext.getResources().getDrawable(R.mipmap.ic_launcher)).getBitmap();
    }


    // Return the launcher intent for this page. This might be used for the launcher as well when the page is disabled?
    @Override
    public Intent getWidgetIntent() {
        //Intent localIntent = new Intent();
        //localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        //localIntent.setAction("android.intent.action.MAIN");
        //localIntent.addCategory("android.intent.category.LAUNCHER");
        //localIntent.setComponent(new ComponentName(this.mContext.getPackageName(), "com.huami.watch.deskclock.countdown.CountdownListActivity"));
        return new Intent();
    }


    // Return the title for this page, used when the page is disabled in the app list. In this case, the app name is used
    @Override
    public String getWidgetTitle(Context paramContext) {
        return this.mContext.getResources().getString(R.string.timeline_name);
    }


    // Save springboard host
    private ISpringBoardHostStub host = null;

    // Returns the springboard host
    public ISpringBoardHostStub getHost() {
        return this.host;
    }

    // Called when the page is loading and being bound to the host
    @Override
    public void onBindHost(ISpringBoardHostStub paramISpringBoardHostStub) {
        // Log.d(widget.TAG, "onBindHost");
        //Store host
        this.host = paramISpringBoardHostStub;
    }


    // Not sure what this does, can't find it being used anywhere. Best leave it alone
    @Override
    public void onReceiveDataFromProvider(int paramInt, Bundle paramBundle) {
        super.onReceiveDataFromProvider(paramInt, paramBundle);
    }


    // Called when the page is destroyed completely (in app mode). Same as the onDestroy method of an activity
    @Override
    public void onDestroy() {
        super.onDestroy();
    }


}

<|start_filename|>app/src/main/java/com/dinodevs/pacecalendarwidget/Constants.java<|end_filename|>
package com.dinodevs.pacecalendarwidget;

public class Constants {
    // Tag for logging purposes.
    public static final String TAG = "PaceCalendarWidget";
    // Version
    public static final String VERSION = "n/a";
    public static final String EVENT_COLOR = "#ffff0000";
    public static final String CALENDAR_DATA = "CustomCalendarData";
    public static final String EVENTS_DATA = "events";
    public static final String HEADER_PATTERN_12H = "hh:mm a\nEEEE, d MMMM";
    public static final String HEADER_PATTERN_24H = "HH:mm\nEEEE, d MMMM";
    public static final String ELEMENT_PATTERN = "EEEE, d MMMM";
    public static final String TIME_PATTERN_12H = "hh:mm a";
    public static final String TIME_PATTERN_24H = "HH:mm";
    public static final String DATE_PATTERN = "dd/MM/yyyy";
    public static final String TIME_FORMAT_SETTING = "time_12_24";
    public static final String TIME_FORMAT_SETTING_DEFAULT = "24";
    public static final String CALENDAR_CONFIG_FILE = "pacecalendar.txt";

    public static final String PREF_CALENDAR_URL = "pref.calendar.url";

}


<|start_filename|>app/src/main/java/com/dinodevs/pacecalendarwidget/iCalSupport.java<|end_filename|>
package com.dinodevs.pacecalendarwidget;

import android.content.Context;
import android.net.wifi.WifiManager;
import android.provider.Settings;
import android.text.format.Time;
import android.util.Log;

import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.filter.Filter;
import net.fortuna.ical4j.filter.PeriodRule;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Dur;
import net.fortuna.ical4j.model.Period;
import net.fortuna.ical4j.model.PeriodList;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.util.MapTimeZoneCache;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.TimeZone;
import java.util.concurrent.ExecutionException;

class iCalSupport {

    iCalSupport() { }

    static boolean checkICSFile(Context context, String URL) {

        Log.d(Constants.TAG, "iCalSupport checkICSFile URL: " + URL);

        boolean result = false;
        File oldFile = new File(context.getExternalFilesDir(null) + File.separator + "calendar.ics");

        if (URL != null && ! URL.isEmpty()) {

            String testURL = URL.toLowerCase();
            try {

                WifiManager wfm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);

                String workDir = null;
                if (context.getExternalFilesDir(null) != null)
                    workDir = context.getExternalFilesDir(null).getAbsolutePath();
                if (context.getExternalFilesDir(null) == null || workDir == null)
                    workDir = context.getCacheDir().getAbsolutePath();

                Log.d(Constants.TAG, "iCalSupport checkICSFile workDir: " + workDir);


                if (wfm != null)
                    result = wfm.isWifiEnabled();

                if (result)
                    result = false;
                else {
                    Log.i(Constants.TAG, "iCalSupport checkICSFile WiFi is off, checking local file...");
                    if (oldFile.exists())
                        return testICSFile(oldFile);
                    else
                        return result;
                }

                if ((testURL.startsWith("http://") || testURL.startsWith("https://")) && (testURL.endsWith("ics"))) {

                    File newFile = new File(workDir + File.separator + "new_calendar.ics");
                    result = new FilesUtil.urlToFile().execute(URL, workDir, "new_calendar.ics").get();
                    if (result) {

                        if (testICSFile(newFile)) {

                            if (oldFile.exists())
                                result = oldFile.delete();

                            if (newFile.exists() && result) {
                                result = newFile.renameTo(oldFile);
                                if (result) {
                                    APsettings settings = new APsettings(Constants.TAG, context);
                                    settings.set(Constants.PREF_CALENDAR_URL, URL);
                                }
                            } else
                                Log.w(Constants.TAG, "iCalSupport checkICSFile error moving newFile: " + newFile.getAbsolutePath());
                        } else {
                            Log.w(Constants.TAG, "iCalSupport checkICSFile bad ICS file!");
                            if (newFile.exists())
                                newFile.delete();
                        }

                    }
                }
            } catch (NullPointerException | InterruptedException | ExecutionException e) {
                Log.e(Constants.TAG, e.getLocalizedMessage(), e);
            }

        } else {
            Log.i(Constants.TAG, "iCalSupport checkICSFile null URL, checking local file...");
            if (oldFile.exists())
                return testICSFile(oldFile);
            else
                return result;
        }

        return result;

    }

    static String getICSCalendarEvents(Context context) {

        int calendar_events_days = 30;
        Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents calendar_events_days: " + calendar_events_days);

        File oldFile = new File(context.getExternalFilesDir(null) + File.separator + "calendar.ics");
        net.fortuna.ical4j.model.Calendar calendar = null;
        if (oldFile.exists()) {
            try {
                Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents getting ics data");
                System.setProperty("net.fortuna.ical4j.timezone.cache.impl", MapTimeZoneCache.class.getName());
                FileInputStream in = new FileInputStream(oldFile.getAbsolutePath());
                CalendarBuilder builder = new CalendarBuilder();
                calendar = builder.build(in);
            } catch (IOException | ParserException e) {
                Log.e(Constants.TAG, e.getLocalizedMessage(), e);
            }
        } else {
            Log.w(Constants.TAG, "iCalSupport getICSCalendarEvents file not found!");
            return null;
        }

        // Run query
        if (calendar != null) {
            Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents listing events");

            // create a period for the filter starting now with a duration of calendar_events_days + 1
            java.util.Calendar today = java.util.Calendar.getInstance();
            today.set(java.util.Calendar.HOUR_OF_DAY, 0);
            today.clear(java.util.Calendar.MINUTE);
            today.clear(java.util.Calendar.SECOND);
            Period period = new Period(new DateTime(today.getTime()), new Dur(calendar_events_days + 1, 0, 0, 0));
            Filter filter = new Filter(new PeriodRule(period));

            ComponentList events = (ComponentList) filter.filter(calendar.getComponents(Component.VEVENT));

            ComponentList eventList = new ComponentList();

            for (Object o : events) {
                VEvent event = (VEvent) o;

                if (event.getProperty("SUMMARY") != null)
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SU: " + event.getProperty("SUMMARY").getValue());
                if (event.getProperty("DESCRIPTION") != null)
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DC: " + event.getProperty("DESCRIPTION").getValue());
                if (event.getProperty("DTSTART") != null)
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DS: " + event.getProperty("DTSTART").getValue());
                Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SD: " + event.getStartDate().getDate().getTime());
                if (event.getProperty("DTEND") != null)
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DE: " + event.getProperty("DTEND").getValue());
                if (event.getProperty("LOCATION") != null)
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event LO: " + event.getProperty("LOCATION").getValue());
                //Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event: " + event.getProperty("URL").getValue());

                if (event.getProperty("RRULE") != null) {
                    PeriodList list = event.calculateRecurrenceSet(period);

                    for (Object po : list) {
                        try {
                            VEvent vEvent = (VEvent) event.copy();
                            Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents SU: " + vEvent.getSummary().getValue());
                            Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents RRULE: " + (Period) po
                                    + " \\ " + ((Period) po).getStart() + " \\ " + ((Period) po).getStart().getTime());
                            //Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents RRULE: " + (Period) po + " \\ " + ((Period) po).getStart().toString() + " \\ " + ((Period) po).getStart().getTime() + " \\ " + ((Period) po).getEnd().getTime());
                            vEvent.getStartDate().setDate(new DateTime(((Period) po).getStart()));
                            vEvent.getEndDate().setDate(new DateTime(((Period) po).getEnd()));
                            eventList.add(vEvent);
                            Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents SD: " + vEvent.getStartDate().getDate());
                        } catch (Exception e) {
                            Log.e(Constants.TAG, e.getLocalizedMessage(), e);
                        }
                    }

                } else
                    eventList.add(event);
            }

            Collections.sort(eventList, new Comparator<VEvent>() {
                public int compare(VEvent o1, VEvent o2) {
                    if (o1.getStartDate().getDate() == null || o2.getStartDate().getDate() == null)
                        return 0;
                    return o1.getStartDate().getDate().compareTo(o2.getStartDate().getDate());
                }
            });

            // Start formulating JSON
            StringBuilder jsonEvents = new StringBuilder("{\"events\":[");

            // Use the cursor to step through the returned records
            for (Object o: eventList) {
                // Get the field values
                VEvent event = (VEvent) o;
                String title = "", description = "", location = "";
                long start = 0, end = 0;

                if (event.getSummary() != null) {
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SU: " + event.getSummary().getValue());
                    title = event.getSummary().getValue();
                }

                if (event.getDescription() != null) {
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DC: " + event.getDescription().getValue());
                    description = event.getDescription().getValue();
                }

                if (event.getStartDate() != null) {
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DS: " + event.getStartDate().getValue());
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SD: " + event.getStartDate().getDate().getTime());
                    start = event.getStartDate().getDate().getTime();
                }

                if (event.getEndDate() != null) {
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event DS: " + event.getStartDate().getValue());
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SD: " + event.getStartDate().getDate().getTime());
                    end = event.getEndDate().getDate().getTime();
                }

                if (event.getLocation() != null) {
                    Log.d(Constants.TAG, "iCalSupport getICSCalendarEvents event SU: " + event.getSummary().getValue());
                    location = event.getLocation().getValue();
                }

                String account = "ical4j";

                if (isEventAllDay(event) && start != 0) {
                    Time timeFormat = new Time();
                    long offset = TimeZone.getDefault().getOffset(start);
                    if (offset < 0)
                        timeFormat.set(start - offset);
                    else
                        timeFormat.set(start + offset);
                    start = timeFormat.toMillis(true);
                    jsonEvents.append("[ \"").append(title).append("\", \"").append(description).append("\", \"").append(start)
                            .append("\", \"").append("null").append("\", \"").append(location).append("\", \"").append(account).append("\"],");
                } else
                    jsonEvents.append("[ \"").append(title).append("\", \"").append(description).append("\", \"").append(start)
                            .append("\", \"").append(end).append("\", \"").append(location).append("\", \"").append(account).append("\"],");
            }

            // Remove last "," from JSON
            if (jsonEvents.substring(jsonEvents.length() - 1).equals(",")) {
                jsonEvents = new StringBuilder(jsonEvents.substring(0, jsonEvents.length() - 1));
            }

            jsonEvents.append("]}");


            // Check if there are new data
            String setting = Settings.System.getString(context.getContentResolver(), Constants.CALENDAR_DATA);
            if (jsonEvents.toString().equals(setting)) {
                // No new data, no update
                Log.d(Constants.TAG, "iCalSupport calendar events: no new data");
                return null;
            }
            // Save new events as last send
            Settings.System.putString(context.getContentResolver(), Constants.CALENDAR_DATA, jsonEvents.toString());

            return jsonEvents.toString();

        } else {
            Log.w(Constants.TAG, "iCalSupport getICSCalendarEvents calendar is null");
            return null;
        }
    }

    private static boolean testICSFile(File file) {

        boolean result = false;

        try {
            System.setProperty("net.fortuna.ical4j.timezone.cache.impl", MapTimeZoneCache.class.getName());

            FileInputStream in = new FileInputStream(file.getAbsolutePath());
            CalendarBuilder builder = new CalendarBuilder();
            net.fortuna.ical4j.model.Calendar calendar = builder.build(in);

            if (calendar != null) {
                result = true;
                if (calendar.getProperty("PRODID") != null)
                    Log.d(Constants.TAG, "iCalSupport testICSFile PRODID: " + calendar.getProperty("PRODID").getValue());
                if (calendar.getProperty("X-WR-CALDESC") != null)
                    Log.d(Constants.TAG, "iCalSupport testICSFile CALDESC: " + calendar.getProperty("X-WR-CALDESC").getValue());
                if (calendar.getProperty("X-WR-CALNAME") != null)
                    Log.d(Constants.TAG, "iCalSupport testICSFile CALNAME: " + calendar.getProperty("X-WR-CALNAME").getValue());
                if (calendar.getProperty("X-WR-TIMEZONE") != null)
                    Log.d(Constants.TAG, "iCalSupport testICSFile TIMEZONE: " + calendar.getProperty("X-WR-TIMEZONE").getValue());
            }

        } catch (NullPointerException | IOException | ParserException e) {
            Log.e(Constants.TAG, e.getLocalizedMessage(), e);
        }

        return  (result);
    }

    private static boolean isEventAllDay(VEvent event){
        return event.getStartDate().toString().contains("VALUE=DATE");
    }

}


<|start_filename|>app/src/main/java/com/dinodevs/pacecalendarwidget/FilesUtil.java<|end_filename|>
package com.dinodevs.pacecalendarwidget;

import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.net.URL;

public class FilesUtil {

    public static String getiCalURL(Context context) {

        String line = null;
        File file = new File (context.getExternalFilesDir(null), Constants.CALENDAR_CONFIG_FILE);
        if (file.exists ()) {
            Log.d(Constants.TAG, String.format("FilesUtil getiCalURL file: %s found", file.getName()));
            try {
                BufferedReader reader = new BufferedReader(new FileReader(file));
                line = reader.readLine();
                line = line.trim();
                Log.d(Constants.TAG, String.format("FilesUtil getiCalURL line: %s", line));

                String test = line.toLowerCase();
                boolean isICSFile = !test.isEmpty() && (test.startsWith("http://") || test.startsWith("https://")) && test.endsWith("ics");
                if (!isICSFile)
                    line = null;

                reader.close();
            }
            catch (Exception e) {
                Log.e(Constants.TAG, e.getLocalizedMessage(), e);

            }
        }
        return line;
    }

    public static class urlToFile extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... strings) {

            String url = strings[0];
            String saveDir = strings[1];
            String file = strings[2];
            Log.d(Constants.TAG, "FilesUtil urlToFile url: " + url + " saveDir: " + saveDir + " file: " + file);

            BufferedInputStream in = null;
            long length = 0;
            try {
                in = new BufferedInputStream(new URL(url).openStream());
                FileOutputStream out = new FileOutputStream(saveDir + File.separator + file);
                byte[] buffer = new byte[1024];
                int c;
                while ((c = in.read(buffer)) != -1) {
                    out.write(buffer, 0, c);
                    length += c;
                }
                out.close();
                in.close();
            } catch (Exception e) {
                Log.e(Constants.TAG, e.getLocalizedMessage(), e);
                return false;
            } finally {
                Log.d(Constants.TAG, "FilesUtil urlToFile length: " + length);
            }
            return true;
        }
    }
}


<|start_filename|>app/src/main/java/com/dinodevs/pacecalendarwidget/Calendar.java<|end_filename|>
package com.dinodevs.pacecalendarwidget;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Locale;

public class Calendar extends Activity {

    // Activity variables
    private Context mContext;
    private Activity activity;

    private View mView;
    private int current_color;
    private TextView current_color_element;

    private APcalendar apcalendar;
    private APtranslations aptranslations;
    private static APsettings settings;

    // Version
    public String version = "n/a";
    // Errors for debugging
    private String errors;

    // Calendar vars
    private Vibrator vibe;
    private java.util.Calendar shown_date;
    private boolean shown_year;
    private boolean show_week;
    public boolean doIvibrate;

    // Set up
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Save Activity variables
        this.mContext = this;
        activity = this;


        setContentView(R.layout.widget_calendar);
        this.mView = this.findViewById(android.R.id.content);

        // Save Activity variables
        //this.mView = LayoutInflater.from(this.mContext).inflate(R.layout.widget_calendar, null);

        //settings = new APsettings(Constants.TAG, mContext);

        //Intent myIntent = getIntent(); // gets the previously created intent
        //this.current_color = myIntent.getIntExtra("color", 0);

        Log.d(Constants.TAG, "Calendar: Starting calendar...");

        // Initialize variables
        this.init();

        Log.d(Constants.TAG, "Calendar: Attaching listeners...");
        // Attach event listeners
        this.initListeners();

        Log.d(Constants.TAG, "Calendar: Done...");
    }

    @Override
    public void onDestroy() {
        Log.d(Constants.TAG, "Calendar onDestroy");
        super.onDestroy();
    }

    // Initialize
    private void init() {
// Get widget version number
        try {
            PackageInfo pInfo = this.mContext.getPackageManager().getPackageInfo(this.mContext.getPackageName(), 0);
            this.version = pInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        // Set date to current
        this.shown_date = java.util.Calendar.getInstance();

        // Init vibration service
        this.vibe = (Vibrator) this.mContext.getSystemService(Context.VIBRATOR_SERVICE);

        // Init variables
        this.apcalendar = null;
        this.aptranslations = null;
        this.current_color_element = null;
        this.settings = null;
        this.errors = "";

        // Load settings
        this.settings = new APsettings(Constants.TAG, mContext);

        // Set default settings
        this.shown_year = this.settings.get("show_year", true);
        this.show_week = this.settings.get("show_week", false);
        this.changeColorByName(this.settings.get("color", "orange"));
        this.doIvibrate = this.settings.get("vibrate", true);

        // Init Calendar
        try {
            // Create calendar
            this.apcalendar = new APcalendar(this.mView, this.mContext, this.shown_date, this.current_color);
        } catch (Exception e) {
            e.printStackTrace();
            this.errors += e.getMessage();
        }

        // Get translations
        this.aptranslations = this.apcalendar.getTranlations();

        // Check lang
        String saved_lang = this.settings.get("lang", "en");
        if (!saved_lang.equals(this.aptranslations.getCode())) {
            this.aptranslations.setLang(saved_lang);
            this.updateLanguage();
        }

        // If monday first
        if (this.settings.get("monday_first", false)) {
            this.changeMondayFirst(true);
        }
    }

    // Attach listeners
    @SuppressLint("ClickableViewAccessibility")
    private void initListeners(){
// About / Errors button event
        TextView about = this.mView.findViewById(R.id.about);
        about.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar.this.toast("Calendar Widget\n v" + Calendar.this.version + "\n\nby GreatApo,\nLFOM & DarkThanos" + (Calendar.this.errors.length() > 0 ? " Errors: " + Calendar.this.errors : ""));
                Calendar.this.vibrate();
            }
        });

        APtouch handler = new APtouch() {
            @Override
            public boolean onSwipeUp() {
                Calendar.this.changeMonth(1);
                Calendar.this.vibrate();
                return true;
            }
            @Override
            public boolean onSwipeDown() {
                Calendar.this.changeMonth(-1);
                Calendar.this.vibrate();
                return true;
            }

            @Override
            public boolean onDoubleClick() {// Not onLongClick
                //public boolean onLongClick() {
                // Open settings
                // Log.d(Constants.TAG, "Pop settings");
                Calendar.this.mView.findViewById(R.id.settings_box).setVisibility(View.VISIBLE);
                Calendar.this.vibrate(50);
                return true;
            }
        };
        ConstraintLayout layout = this.mView.findViewById(R.id.container);
        layout.setOnTouchListener(handler.listen());

        // Open Settings button
        ImageView open_settings_button = this.mView.findViewById(R.id.settings_icon);
        open_settings_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar.this.mView.findViewById(R.id.settings_box).setVisibility(View.VISIBLE);
                Calendar.this.vibrate(50);
            }
        });

        // Close Settings button
        TextView close_settings_button = this.mView.findViewById(R.id.close_settings);
        close_settings_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar.this.mView.findViewById(R.id.settings_box).setVisibility(View.GONE);
                Calendar.this.vibrate();
            }
        });

        // Change Color Buttons
        View.OnClickListener onColorChange = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar.this.onColorChangeBtn(v);
            }
        };
        this.mView.findViewById(R.id.color1).setOnClickListener(onColorChange);
        this.mView.findViewById(R.id.color2).setOnClickListener(onColorChange);
        this.mView.findViewById(R.id.color3).setOnClickListener(onColorChange);
        this.mView.findViewById(R.id.color4).setOnClickListener(onColorChange);
        this.mView.findViewById(R.id.color5).setOnClickListener(onColorChange);
        this.mView.findViewById(R.id.color6).setOnClickListener(onColorChange);

        // Next Translation
        this.mView.findViewById(R.id.language).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar.this.rotateLanguage();
                Calendar.this.vibrate();
            }
        });

        // Year switch
        CheckBox year_checkbox = this.mView.findViewById(R.id.year_switch);
        year_checkbox.setChecked(this.shown_year);
        this.changeYearVisibility(this.shown_year);
        year_checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Calendar.this.changeYearVisibility(isChecked);
                Calendar.this.vibrate();
            }
        });

        // Monday switch (first day of the week)
        boolean isMondayFirst = this.settings.get("monday_first", false);
        CheckBox monday_first_checkbox = this.mView.findViewById(R.id.monday_switch);
        monday_first_checkbox.setChecked(isMondayFirst);
        monday_first_checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Calendar.this.changeMondayFirst(isChecked);
                Calendar.this.vibrate();
            }
        });

        // Vibration switch
        CheckBox vibrate_checkbox = this.mView.findViewById(R.id.vibrate_switch);
        vibrate_checkbox.setChecked(this.doIvibrate);
        vibrate_checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Calendar.this.changeVibration(isChecked);
                Calendar.this.vibrate();
            }
        });

        // Week switch (show week number)
        CheckBox week_checkbox = this.mView.findViewById(R.id.week_switch);
        week_checkbox.setChecked(this.show_week);
        this.changeWeekVisibility(this.show_week);
        week_checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Calendar.this.changeWeekVisibility(isChecked);
                Calendar.this.vibrate();
            }
        });

        // Refresh current date
        ImageView refresh_button = this.mView.findViewById(R.id.refresh);
        refresh_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Refresh View
                Calendar.this.refreshView();
                // Show date
                Calendar.this.toast(Calendar.this.dateToString(java.util.Calendar.getInstance()));
                Calendar.this.vibrate();
            }
        });

        // Prev Month button
        TextView previous_month_button = this.mView.findViewById(R.id.arrow_up);
        previous_month_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar.this.changeMonth(-1);
                Calendar.this.vibrate();
            }
        });

        // Next Month button
        TextView next_month_button = this.mView.findViewById(R.id.arrow_down);
        next_month_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar.this.changeMonth(1);
                Calendar.this.vibrate();
            }
        });

        //Timeline
        ImageView events = this.mView.findViewById(R.id.events_icon);
        events.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar.this.vibrate();
                toast("Loading events...");
                final Intent timelineIntent = new Intent(mContext, Timeline.class);
                timelineIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                        Intent.FLAG_ACTIVITY_CLEAR_TOP);
                timelineIntent.putExtra("color",Calendar.this.current_color);
                mContext.startActivity(timelineIntent);
            }
        });
    }

    // Change widget color
    private void changeColor(int id, int color) {
        // Uncheck old color
        if (this.current_color_element != null)
            this.current_color_element.setText(" ");

        // Save and check new color
        this.current_color = this.mContext.getResources().getColor(color);
        this.current_color_element = this.mView.findViewById(id);
        this.current_color_element.setText("✔");

        // Change close settings color
        ((GradientDrawable) this.mView.findViewById(R.id.close_settings).getBackground()).setColor(current_color);

        // Reload calendar with the new color
        if (this.apcalendar != null)
            this.apcalendar.refresh(this.shown_date, this.current_color);

        // Save settings
        this.settings.set("color", this.parseColorToString(color));
    }
    private void changeColorByName(String color_name) {
        switch (color_name) {
            case "red":
                this.changeColor(R.id.color1, R.color.basecolor1);
                return;
            case "blue":
                this.changeColor(R.id.color2, R.color.basecolor2);
                return;
            case "green":
                this.changeColor(R.id.color3, R.color.basecolor3);
                return;
            case "purple":
                this.changeColor(R.id.color4, R.color.basecolor4);
                return;
            case "orange":
                this.changeColor(R.id.color5, R.color.basecolor5);
                return;
            case "yellow":
                this.changeColor(R.id.color6, R.color.basecolor6);
                return;
        }
        this.changeColor(R.id.color5, R.color.basecolor5);
    }

    private String parseColorToString(int color) {
        switch (color) {
            case R.color.basecolor1:
                return "red";
            case R.color.basecolor2:
                return "blue";
            case R.color.basecolor3:
                return "green";
            case R.color.basecolor4:
                return "purple";
            case R.color.basecolor5:
                return "orange";
            case R.color.basecolor6:
                return "yellow";
        }
        return "orange";
    }

    // Change month by n
    private void changeMonth(int n) {
        // Move n months
        this.shown_date.add(java.util.Calendar.MONTH, n);
        // Show month
        this.apcalendar.refresh(this.shown_date);
    }

    private void onColorChangeBtn(View v) {
        // Find caller and change to the appropriate color
        switch (v.getId()) {
            case R.id.color1: this.changeColor(R.id.color1, R.color.basecolor1); break;
            case R.id.color2: this.changeColor(R.id.color2, R.color.basecolor2); break;
            case R.id.color3: this.changeColor(R.id.color3, R.color.basecolor3); break;
            case R.id.color4: this.changeColor(R.id.color4, R.color.basecolor4); break;
            case R.id.color5: this.changeColor(R.id.color5, R.color.basecolor5); break;
            case R.id.color6: this.changeColor(R.id.color6, R.color.basecolor6); break;
        }
        this.vibrate();
    }

    // Show/Hide year
    private void changeYearVisibility(boolean visible){
        // If show year
        if (visible) {
            this.shown_year = true;
            this.mView.findViewById(R.id.textYear).setVisibility(View.VISIBLE);
        }
        // If hide year
        else {
            this.shown_year = false;
            this.mView.findViewById(R.id.textYear).setVisibility(View.GONE);
        }

        // Save setting
        this.settings.set("show_year", this.shown_year);
    }

    // Show/Hide week number
    private void changeWeekVisibility(boolean visible){
        // If show week
        if (visible) {
            this.show_week = true;
            this.mView.findViewById(R.id.week1).setVisibility(View.VISIBLE);
            this.mView.findViewById(R.id.week2).setVisibility(View.VISIBLE);
            this.mView.findViewById(R.id.week3).setVisibility(View.VISIBLE);
            this.mView.findViewById(R.id.week4).setVisibility(View.VISIBLE);
            this.mView.findViewById(R.id.week5).setVisibility(View.VISIBLE);
            this.mView.findViewById(R.id.week6).setVisibility(View.VISIBLE);
        }
        // If hide week
        else {
            this.show_week = false;
            this.mView.findViewById(R.id.week1).setVisibility(View.GONE);
            this.mView.findViewById(R.id.week2).setVisibility(View.GONE);
            this.mView.findViewById(R.id.week3).setVisibility(View.GONE);
            this.mView.findViewById(R.id.week4).setVisibility(View.GONE);
            this.mView.findViewById(R.id.week5).setVisibility(View.GONE);
            this.mView.findViewById(R.id.week6).setVisibility(View.GONE);
        }

        // Save setting
        this.settings.set("show_week", this.show_week);
    }

    // Sunday/Monday first day of the week
    private void changeMondayFirst(boolean mondayFirst){
        this.apcalendar.isMondayFirst = mondayFirst;
        this.apcalendar.refresh();
        this.apcalendar.refreshDays();

        // Save setting
        this.settings.set("monday_first", mondayFirst);
    }


    // Vibration
    private void changeVibration(boolean vibrate){
        this.doIvibrate = vibrate;

        // Save setting
        this.settings.set("vibrate", vibrate);
    }

    // Change to the next language
    private void rotateLanguage() {
        // Load next lang
        this.aptranslations.nextLang();
        // Show ui in new language
        this.updateLanguage();
        // Save lang
        this.settings.set("lang", this.aptranslations.getCode());
    }

    private void updateLanguage() {
        // Update language name text
        ((TextView) this.mView.findViewById(R.id.language)).setText(
                this.aptranslations.getName()
        );

        TextView[] view_other = new TextView[]{
                this.mView.findViewById(R.id.select_color),
                this.mView.findViewById(R.id.year_switch),
                this.mView.findViewById(R.id.monday_switch),
                this.mView.findViewById(R.id.vibrate_switch),
                this.mView.findViewById(R.id.week_switch)
        };

        String[] other = this.aptranslations.getOther();
        for (int i = view_other.length - 1; i >= 0; i--) {
            view_other[i].setText(other[i]);
        }

        // Reload day names
        this.apcalendar.refreshDays();
        // Refresh calendar UI (month name etc)
        this.apcalendar.refresh();
        //this.apcalendar.refreshMonthName();
    }


    // Refresh Calendar (set it to current month)
    private void refreshView() {
        // Get current date
        java.util.Calendar now = java.util.Calendar.getInstance();
        this.shown_date = now;

        // Refresh calendar
        this.apcalendar.refresh(now);
    }


    // Vibrator wrappers
    private void vibrate () {
        if(doIvibrate)
            this.vibrate(10);
    }
    private void vibrate (long milliseconds) {
        if(doIvibrate)
            this.vibe.vibrate(milliseconds);
    }

    // Toast wrapper
    private void toast (String message) {
        Toast toast;
        toast = Toast.makeText(this.mContext, message, Toast.LENGTH_SHORT);
        TextView v = toast.getView().findViewById(android.R.id.message);
        if( v != null) v.setGravity(Gravity.CENTER);
        toast.show();
    }

    // Convert a date to dd/MM/yyyy format
    private String dateToString (java.util.Calendar date) {
        return (new SimpleDateFormat("dd/MM/yyyy", Locale.US)).format(date.getTime());
    }

}



<|start_filename|>app/src/main/java/com/dinodevs/pacecalendarwidget/Timeline.java<|end_filename|>
package com.dinodevs.pacecalendarwidget;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;

public class Timeline extends Activity {

    // Activity variables
    private Context mContext;
    private Activity activity;

    private View mView;
    private ListView lv;
    private TextView time;
    private ImageView refresh;
    private int current_color;

    private ArrayList<HashMap<String, String>> eventsList;

    private String header_pattern;
    private String time_pattern;
    private static String toastMsg;

    private static final String TITLE = "title";
    private static final String SUBTITLE = "subtitle";
    private static final String DOT = "dot";

    private static String calendarEvents;
    private static APsettings settings;

    // Set up
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Save Activity variables
        this.mContext = this;
        activity = this;
        setContentView(R.layout.widget_timeline);
        this.mView = this.findViewById(android.R.id.content);

        settings = new APsettings(Constants.TAG, mContext);

        Intent myIntent = getIntent(); // gets the previously created intent
        this.current_color = myIntent.getIntExtra("color", 0);

        // Initialize variables
        Log.d(Constants.TAG, "Timeline: Starting...");
        this.init();

        // Attach event listeners
        Log.d(Constants.TAG, "Timeline: Attaching listeners...");
        this.initListeners();

        // Finish
        Log.d(Constants.TAG, "Timeline: Done...");
    }

    @Override
    public void onDestroy() {
        Log.d(Constants.TAG, "Timeline onDestroy");
        super.onDestroy();
    }

    // Initialize
    private void init() {

        boolean is24h = Settings.System.getString(mContext.getContentResolver(), Constants.TIME_FORMAT_SETTING).equals(Constants.TIME_FORMAT_SETTING_DEFAULT);
        Log.d(Constants.TAG, "Timeline init is24h: " + is24h);

        if (is24h) {
            header_pattern = Constants.HEADER_PATTERN_24H;
            time_pattern = Constants.TIME_PATTERN_24H;
        } else {
            header_pattern = Constants.HEADER_PATTERN_12H;
            time_pattern = Constants.TIME_PATTERN_12H;
        }

        // Show Time/Date
        time = this.mView.findViewById(R.id.time);
        refresh_time();


        // Refresh button
        refresh = this.mView.findViewById(R.id.refresh);

        // Calendar Events Data
        eventsList = new ArrayList<>();
        lv = this.mView.findViewById(R.id.list);
        loadCalendarEvents();
    }

    // Attach listeners
    @SuppressLint("ClickableViewAccessibility")
    private void initListeners(){

        // About button event
        /*
        time.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                refresh_time();
                Timeline.this.toast("Calendar Widget v" + Timeline.this.version + " by GreatApo, LFOM & DarkThanos");
            }
        });

        // Refresh events
        time.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                refresh_time();
                loadiCalData();
                return true;
            }
        });
        */

        // Refresh events
        refresh.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                refresh_time();
                loadiCalData();
            }
        });

        // Scroll to top
        TextView top = this.mView.findViewById(R.id.backToTop);
        top.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ListView list = Timeline.this.mView.findViewById(R.id.list);
                list.setSelectionAfterHeaderView();
                //Timeline.this.toast("▲ Top");
            }
        });

    }

    private void refresh_time(){
        TextView time = this.mView.findViewById(R.id.time);
        time.setText(dateToString(Calendar.getInstance(), header_pattern));
    }

    private void loadCalendarEvents() {
        eventsList = new ArrayList<>();
        long next_event = 0;

        // Load data
        calendarEvents = Settings.System.getString(mContext.getContentResolver(), Constants.CALENDAR_DATA);

        // Default when undefined/empty
        if (calendarEvents == null || calendarEvents.isEmpty()) {
            calendarEvents = "{\"events\":[]}";
            Timeline.this.toast("Empty or null events data!", true);
        }

        // Read Data
        try {
            // Check if correct form of JSON
            JSONObject json_data = new JSONObject(calendarEvents);

            // If there are events
            if (json_data.has(Constants.EVENTS_DATA)) {
                int event_number = json_data.getJSONArray(Constants.EVENTS_DATA).length();

                Calendar calendar = Calendar.getInstance();
                calendar.add(Calendar.MINUTE,-10); // Show only future events + 10 minutes old
                long current_time = calendar.getTimeInMillis();
                String current_loop_date = "";

                // Get data
                for(int i=0; i<event_number; i++) {
                    JSONArray data = json_data.getJSONArray(Constants.EVENTS_DATA).getJSONArray(i);

                    HashMap<String, String> event = new HashMap<>();
                    event.put(DOT, mContext.getResources().getString(R.string.bull) );

                    // adding each child node to HashMap key => value
                    event.put(TITLE, data.getString(0));
                    //event.put("description", data.getString(1));
                    //event.put("start", data.getString(2));
                    //event.put("end", data.getString(3));
                    //event.put("location", data.getString(4));
                    //event.put("account", data.getString(5));

                    // Get event "start" data
                    String start;
                    if(!data.getString(2).equals("") && !data.getString(2).equals("null")) {
                        calendar.setTimeInMillis(Long.parseLong(data.getString(2)));

                        if(current_time > calendar.getTimeInMillis()){
                            // Event expired, go to next
                            continue;
                        }

                        // Mark first next event
                        if( next_event == 0 )
                            next_event = calendar.getTimeInMillis();// TODO not used

                        start = dateToString(calendar, time_pattern);

                        // Insert day separator, or not :P
                        if( !current_loop_date.equals(dateToString( calendar,Constants.ELEMENT_PATTERN )) ){
                            current_loop_date = dateToString(calendar, Constants.ELEMENT_PATTERN);

                            HashMap<String, String> date_elem = new HashMap<>();
                            date_elem.put(TITLE, "");
                            date_elem.put(SUBTITLE, (current_loop_date.equals(dateToString(Calendar.getInstance(), Constants.ELEMENT_PATTERN)))?getString(R.string.today):current_loop_date );
                            date_elem.put(DOT, "" );
                            eventsList.add(date_elem);
                        }
                    }else{
                        // Event has no date, go to next
                        continue;
                    }

                    // Get event "end" data
                    String end = "";

                    // All day events
                    if((start.startsWith("00") || start.startsWith("12")) && ("null".equals(data.getString(3)) || data.getString(3).isEmpty())) {
                        start = getString(R.string.all_day);
                    }else{
                        if(!data.getString(3).equals("") && !data.getString(3).equals("null")) {
                            calendar.setTimeInMillis(Long.parseLong(data.getString(3)));
                            end = " - "+ dateToString(calendar, time_pattern);
                        }
                    }

                    // Get location data
                    String location = "";
                    if(!data.getString(4).equals("") && !data.getString(4).equals("null"))
                        location = "\n@ "+data.getString(4);

                    event.put(SUBTITLE, start + end + location );

                    // Add event to events list
                    eventsList.add(event);
                }
            }else{
                Timeline.this.toast("No events data in JSON.", true);
            }
        } catch (JSONException e) {
            Timeline.this.toast("Corrupted data!", true);
        }

        // No events message
        if(eventsList.isEmpty()){
            HashMap<String, String> elem = new HashMap<>();
            elem.put("title", "\n"+mContext.getResources().getString(R.string.no_events));
            elem.put("subtitle", mContext.getResources().getString(R.string.no_events_description));
            elem.put("dot", "" );
            eventsList.add(elem);
        }

        ListAdapter adapter = new SimpleAdapter(mContext, eventsList, R.layout.list_item, new String[]{"title", "subtitle", "dot"}, new int[]{R.id.title, R.id.description, R.id.dot}){
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {

                View v = convertView;
                if(v== null){
                    LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    v=vi.inflate(R.layout.list_item, null);
                }

                TextView title = v.findViewById(R.id.title);
                TextView subtitle = v.findViewById(R.id.description);
                TextView dot = v.findViewById(R.id.dot);
                LinearLayout header = v.findViewById(R.id.header);

                title.setText(eventsList.get(position).get("title"));
                subtitle.setText(eventsList.get(position).get("subtitle"));
                dot.setText(eventsList.get(position).get("dot"));

                if(Timeline.this.current_color!=0) {
                    dot.setTextColor(Timeline.this.current_color);

                    // Day separator
                    if(eventsList.get(position).get("dot").isEmpty() && eventsList.get(position).get("title").isEmpty())
                        subtitle.setTextColor(Timeline.this.current_color);
                    else
                        subtitle.setTextColor(getResources().getColor(R.color.description));
                }

                return v;
            }
        };
        lv.setAdapter(adapter);
    }

    private void loadiCalData() {
        final String icalURL = FilesUtil.getiCalURL(mContext);
        new Thread(new Runnable() {
            @Override
            public void run() {
                    toastMsg = "Getting iCal data,\nplease wait...";
                    activity.runOnUiThread(showToast);
                    if (iCalSupport.checkICSFile(mContext, icalURL)) {
                        calendarEvents = iCalSupport.getICSCalendarEvents(mContext);
                        toastMsg = "Refreshing events...";
                        activity.runOnUiThread(showToast);

                    } else {
                        toastMsg = "\niCal data not found.\nPlease write/place your ICS feed URL/file at:\n/sdcard/Android/data/com.dinodevs.pacecalendarwidget/files/pacecalendar.txt";
                        activity.runOnUiThread(showToast);
                    }

                    lv.post(loadEvents);

                    /*
                    if (calendarEvents == null) {
                        toastMsg = "No new events!";
                        activity.runOnUiThread(showToast);
                        settings.set(Constants.PREF_CALENDAR_URL, "");
                    } else {
                        toastMsg = "Refreshing events...";
                        activity.runOnUiThread(showToast);
                        lv.post(loadEvents);
                    }
                    */
            }
        }).start();
    }

    private final Runnable showToast = new Runnable() {
        public void run() {
            toast(toastMsg, false);
        }
    };

    private final Runnable loadEvents = new Runnable() {
        public void run() {
            loadCalendarEvents();
        }
    };

    // Toast wrapper
    private void toast (String message, boolean shortTime) {
        Toast toast;
        if (shortTime)
            toast = Toast.makeText(this.mContext, message, Toast.LENGTH_SHORT);
        else
            toast = Toast.makeText(this.mContext, message, Toast.LENGTH_LONG);
        TextView v = toast.getView().findViewById(android.R.id.message);
        if( v != null) v.setGravity(Gravity.CENTER);
        toast.show();
    }

    // Convert a date to format
    private String dateToString (Calendar date) {
        return (new SimpleDateFormat(Constants.DATE_PATTERN, Locale.US)).format(date.getTime());
    }
    private String dateToString (Calendar date, String pattern) {
        return (new SimpleDateFormat(pattern, Locale.US)).format(date.getTime());
    }

}

