Free Online Courses for Software Developers - MrBool
× Please, log in to give us a feedback. Click here to login
×

You must be logged to download. Click here to login

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

Task scheduler APP in Android

This article will teach you how to create a task scheduler app in android which will save the created Task in SQLite database and it will also show the reminder to the user even if the TaskScheduler app is not running.

First of all, let’s concentrate on the scenario:

You have created the UI of the Add task activity in the Task Scheduler app. You have also implemented some validations on the data entered by the user. Now, you wants that on clicking the Save button, the data entered by the user should be stored in a database and the task should be displayed in a new activity named Task List. The Task List activity lists all the tasks scheduled by the user, as shown in the following figure.

The Task List Activity

Figure 1: The Task List Activity

On clicking a task in the Task List activity, the details of the task should be displayed, as shown in the following figure.

The Task List Activity Displaying the Task Details

Figure 2: The Task List Activity Displaying the Task Details

Further, the message should be displayed even if the Task Scheduler app is not running and you wants to make the Task List activity as the main activity of the app so that the Task List activity is the first activity displayed to the user when the user launches the app. When no tasks have been added, the Task List activity should be displayed, as shown in the following figure.

The Task List Activity with No Tasks

Figure 3: The Task List Activity with No Tasks

Solutions

To understand this code you need to refer my previous article about the Database connectivity in android and the broadcast receiver. There you will get a brief idea about the internal theory about this app.

To start-up with this project you need to start with the design process of the app UI. Mainly, there will be two UIs: one tasklist and the another one is notask. Below you can see the coding of both the UIs.

Listing 1: The Task List UI

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent" android:orientation="vertical" android:background="#a4b8cc">
    <ListView android:layout_height="match_parent" android:id="@id/android:list" android:layout_weight="1" android:layout_width="match_parent"></ListView>
	<LinearLayout android:layout_height="wrap_content" android:layout_width="match_parent" android:id="@+id/linearLayout1" android:background="#535352" android:gravity="center|bottom" android:layout_gravity="bottom" android:layout_weight="0">
        <Button android:text="Add Task" android:textColor="#535352" android:textSize="30sp" android:layout_height="wrap_content" android:id="@+id/tasklistbutton1" android:layout_width="wrap_content" android:onClick="myClickHandler"></Button>
    </LinearLayout>
</LinearLayout>

In Listing 1, the UI is consist of one ListBox and a Button with it. The use of this UI is to create the Task by clicking on the button. The UI diagram is mention above. The list box of this Listing will contain the Task List which is already created in the app.

Listing 2: The No Task UI:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent" android:orientation="vertical" android:background="#a4b8cc">
    <LinearLayout android:layout_height="wrap_content" android:layout_weight="1" android:layout_width="match_parent" android:id="@+id/linearLayout2" android:orientation="vertical">
        <TextView android:text="No Task Available" android:textColor="#3d4042" android:gravity="center_horizontal" android:id="@+id/textView1" android:layout_height="wrap_content" android:layout_width="match_parent" android:textSize="30sp" android:layout_weight="0"></TextView>
        <ListView android:layout_height="match_parent" android:layout_width="match_parent" android:id="@id/android:list" android:layout_weight="10"></ListView>
    </LinearLayout>
    <LinearLayout android:layout_height="wrap_content" android:layout_width="match_parent" android:id="@+id/linearLayout1" android:background="#535352" android:gravity="center|bottom" android:layout_gravity="bottom" android:layout_weight="0.01">
        <Button android:text="Add Task" android:textColor="#535352" android:textSize="30sp" android:layout_height="wrap_content" android:id="@+id/notaskbutton1" android:layout_width="wrap_content" android:onClick="myClickHandler"></Button>
    </LinearLayout>
</LinearLayout>

The above listing is for the NoTask UI which is used when the app does not having any task associate with it.

Once both the UIs are created, then you can start writing the back-end codes related with the events of those UI controls.

Please also note that, meanwhile you also need to modify the AndroidManifest.xml. The main code segment is given below.

Here you need to change the app configuration so that the app can show the notification to the user even the app is not running in the app container.

Listing 3: Changing the APP configuration

//Providing the App Component Name to which the functionality will added.
        <provider android:name="TaskSchedulerContentProvider"
         android:authorities="com.taskscheduler.provider.Task"/>
        <service android:name=".ExpireTimeService"></service>  
// App Config is receiving the notification in offline state.
        <receiver android:name=".BootCompleteReciever" 
 			android:enabled="true" 
 			android:exported="false"
 			android:label="BootCompleteReciever">
   	<intent-filter>
// Its getting attached with the boot process with the device.
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

Once this is done then you can start creating the below mention five files, which are:

  • BootCompleteReciever.java
  • ExpireTimeService.java
  • TaskListActivity.java
  • TaskSchedulerActivity.java
  • TaskSchedulerContentProvider.java

Which are also available in the attached project. The location for the same is: TaskScheduler\src\com\example\taskscheduler

The BootCompleteREceiver.java file is responsible to send the notification even the app is not running. The code for the same is given bellow.

Listing 4: BootCompleteReciever.java

package com.example.taskscheduler;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class BootCompleteReciever extends BroadcastReceiver {

	public static final String TAG = "ExpireTimeServiceManager";
	 @Override
	 public void onReceive(Context context, Intent intent) {
	  // just make sure we are getting the right intent (better safe than sorry)
	  if( "android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) {
	   ComponentName comp = new ComponentName(context.getPackageName(),ExpireTimeService.class.getName());
	   ComponentName service = context.startService(new Intent().setComponent(comp));
	   if (null == service){
	    // something really wrong here
	    Log.e(TAG, "Could not start service " + comp.toString());
	   }
	  } else {
	   Log.e(TAG, "Received unexpected intent " + intent.toString());   
	  }
	 }
}

The TaskSchedulerContentProvider.java is used to provide the framework and infrastructure to create the tasks with the help of TaskSchedulerActivity.java file. Code for the same is as follows.

As you know, this component is going to add the tasks to DB hence you need to import all the required packages for the same.

Listing 5: Required Packages

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

Declare all the required variables and objects in the class level to get accessed by all the methods. Please also ensure that you class should inherit the ContentProvider Class. :

Listing 6: Declaring variables

	public static final String _ID = "id";
	public static final String NAME = "task";
	//private static final String TAG = "TaskSchedulerContentProvider";		 
	private static final String DATABASE_NAME = "TaskScheduler.db";
	private static final int DATABASE_VERSION = 1;
	private static final String TABLE_NAME = "Task";	 
	public static final String PROVIDER_NAME = 
	      "com.taskscheduler.provider.Task";
	public static final Uri CONTENT_URI = 
	      Uri.parse("content://"+ PROVIDER_NAME + "/tasks");		 
	private static final int ALLTASK = 1;
	private static final int TASK_ID = 2;
	private SQLiteDatabase taskDB;

Create another class called OpenHelper and it should inherit the SQLiteOpenHelper class. This class will create the required table for this project. The onCreate method will do the same.

Listing 7: Creating Class OpenHelper

public static class OpenHelper extends SQLiteOpenHelper {

	      OpenHelper(Context context) {
	         super(context, DATABASE_NAME, null, DATABASE_VERSION);
	      }

	      @Override
	      public void onCreate(SQLiteDatabase db) {
	         db.execSQL("CREATE TABLE " + TABLE_NAME + "(id INTEGER PRIMARY KEY, key TEXT, task TEXT, start TEXT, end TEXT, time TEXT, alarm TEXT)");
	      }

You also need to override the delete method of OpenHelper class to clear all the unwanted resources.

Listing 8: Overriding

	@Override
	public int delete(Uri arg0, String arg1, String[] arg2) {
		// TODO Auto-generated method stub
		int count=0;
	      switch (uriMatcher.match(arg0))
{
	         case ALLTASK:
	            count = taskDB.delete(TABLE_NAME, arg1, arg2);
	            break;
	         case TASK_ID:
	            String id = arg0.getPathSegments().get(1);
count = taskDB.delete(TABLE_NAME, _ID + " = " + id + (!TextUtils.isEmpty(arg1) ? " AND ("arg1 + ')' : ""), arg2);
	            break;
	         default: throw new IllegalArgumentException("Unknown URI " + arg0);    
	      }       
	      getContext().getContentResolver().notifyChange(arg0, null);
	      return count;	
	}
	@Override
	public Uri insert(Uri arg0, ContentValues arg1) { 
		// TODO Auto-generated method stub
		long rowID = taskDB.insert(
		         TABLE_NAME, "", arg1);
		 
		      //---if added successfully---
		      if (rowID>0)
		      {
		         Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
		         getContext().getContentResolver().notifyChange(_uri, null);    
		         return _uri;                
		      }        
		      throw new SQLException("Failed to insert row into " + arg0);
	}

The above listing will insert the task information to the DataBase.

Listing 9: Update Purpose

	@Override
	public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
		// TODO Auto-generated method stub
		int count = 0;
	      switch (uriMatcher.match(arg0)){
	         case ALLTASK:
	            count = taskDB.update(TABLE_NAME, arg1, arg2, arg3);
	            break;
	         case TASK_ID:                
	            count = taskDB.update(TABLE_NAME, arg1, _ID + " = " + arg0.getPathSegments().get(1) + (!TextUtils.isEmpty(arg2) ? " AND (" +arg2 + ')' : ""), arg3);
	            break;
	         default: throw new IllegalArgumentException("Unknown URI " + arg0);    
	      }       
	      getContext().getContentResolver().notifyChange(arg0, null);
	      return count;
	}

The above listing will be used for the update purpose. In case the task needs to be edited, it will update the same to the DB.

ExpireTimeService.java is used to take care when to start the task notification and send the signal to the Receiver Class.

Listing 10: ExpireTimeService.java

@Override public void onCreate()
{
super.onCreate();
fetchData();
//removes previous call backs from the handler
handler.removeCallbacks(updateTimeTask);
//executes the run method of the thread after the specified time
handler.postDelayed(updateTimeTask, 1000);
}

In the above listing will be created a Thread which will start the checking. This task is going to be expire to show the alarm.

Listing 11: Creating Thread

public void fetchData()
{
Date curr_date = new java.util.Date();
Calendar cal = Calendar.getInstance();       // get calendar instance
cal.setTime(curr_date);                      // set cal to date
cal.set(Calendar.HOUR_OF_DAY, 0);            // set hour to midnight
cal.set(Calendar.MINUTE, 0);                 // set minute in hour
cal.set(Calendar.SECOND, 0);                 // set second in minute
cal.set(Calendar.MILLISECOND, 0);            // set millis in second 
curr_date = cal.getTime();             		 // actually computes the new Date
String columns[] = new String[] {"task", "start", "end", "time" ,"alarm"};
Uri taskuri = Uri.parse("content://com.taskscheduler.provider.Task/tasks/");
Cursor c = getApplicationContext().getContentResolver().query(taskuri, columns, "start='"+curr_date.toString()+"'", null, null);
if(c.moveToFirst())
{
do
{
// Get the field values
tData.add(c.getString(c.getColumnIndex("task"))+";"+c.getString(c.getColumnIndex("start"))+";"+c.getString(c.getColumnIndex("end"))+";"+c.getString(c.getColumnIndex("time"))+";"+c.getString(c.getColumnIndex("alarm")));
			Log.i("TDATA",tData.toString());
			}while(c.moveToNext());
		}	
		if (c != null && !c.isClosed()){
            c.close();
        }
		data =new String[tData.size()][5];
		if(!tData.isEmpty()){
			for(int i=0; i<tData.size();i++){
				breakString(tData.get(i));
				data[i][0]=str1;
				data[i][1]=str2;
				data[i][2]=str3;
				data[i][3]=str4+":00";
				data[i][4]=str5;				
			}
		}
	}

The above listing is fetching the tasks which completed from the DB and arranging the format of the task to get displayed.

Here in the set() is assigning the values to the properties of the Calendar Class.

Listing 12: Assigning Values

	public void stopService()
{
		stopSelf();
	}
	//creates a new thread
	private Runnable updateTimeTask = new Runnable() 
{
		//Runs the Thread
		public void run(){
			try {
String time = android.text.format.DateFormat.format("k:m:ss", new java.util.Date()).toString();
for(int i=0; i<tData.size();i++)
{
		if(data[i][3].equals(time))
{
Toast.makeText(ExpireTimeService.this, "Task "+data[i][0]+" expired.", Toast.LENGTH_SHORT).show();	
}
}
handler.postDelayed(this, 1000);
} catch (Exception e) {
			// TODO Auto-generated catch block
			Log.e("Error from service", e.toString());
			}					
		}		
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		if (handler != null)
			handler.removeCallbacks(updateTimeTask);
		handler = null;
	}
/*Creates a handler object, this class provide flexible implementation of threads in Android. Functionally threads created through this method are similar to creating normal threads*/
	private Handler handler = new Handler();
}

The next listing is going to create the UI, which will show the Task Data in list format. To do this few required packages are imported. One of the most important part of this UI is to show the same in a list format it needs to use one array widgets. The required packages are also imported for the same.

Listing 13: Creating the UI TaskListActivity.java

package com.example.taskscheduler;
import java.util.ArrayList;
import java.util.List;

import android.app.ListActivity;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

The class is created to control the entire list management. Few methods of this class is described below.

  • onCreate: as soon as the task will be created form the respective UI
  • refreshList: It will use to reload the list data after each list related activity, like one Task is completed then it will change the list display accordingly.

Listing 14: Controlling list management

public class TaskListActivity extends ListActivity 
{
private ListView tList;
	private List<String> tData = new ArrayList<String>();
				
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tasklist);
    	tList = (ListView)findViewById(android.R.id.list);
        String columns[] = new String[] {"task"};
Uri movie = Uri.parse("content://com.taskscheduler.provider.Task/tasks/");
ContentResolver cr = getContentResolver();
Cursor c = cr.query(movie, columns, null, null, null );
//Cursor c = managedQuery(movie, columns, null, null, null);
if (c.moveToFirst()) {
			do {
				// Get the field values
				tData.add(c.getString(c
						.getColumnIndex("task")));				
			} while (c.moveToNext());
		}
		if (c != null && !c.isClosed()) {
            c.close();}
		ArrayAdapter<String> taskList = new ArrayAdapter<String>(this,R.layout.list, tData);
        setListAdapter(taskList);
        if(tData.isEmpty()){
        	setContentView(R.layout.notask);
			tList = (ListView)findViewById(android.R.id.list);
			tList.setVisibility(0);               
        }
	}

Listing 15: Creating the component which works together with TaskSchedulerContentProvider.java

	public void refershList(){
		setContentView(R.layout.tasklist);
		tData.clear();		
		String columns[] = new String[] {"task"};
		Uri task = Uri.parse("content://com.taskscheduler.provider.Task/tasks/");
		ContentResolver cr = getContentResolver();

		Cursor c = cr.query(task, columns, null, null, null );

		//Cursor c = managedQuery(task, columns, null, null, null);
		if(c.moveToFirst()){
			do {
				// Get the field values
				tData.add(c.getString(c
						.getColumnIndex("task")));				
			}while (c.moveToNext());
		}
		if (c != null && !c.isClosed()){
            c.close();
        }
		ArrayAdapter<String> taskList = new ArrayAdapter<String>(this,R.layout.list, tData);
        setListAdapter(taskList);
		if(tData.isEmpty()){
        	setContentView(R.layout.notask);
			tList = (ListView)findViewById(android.R.id.list);
			tList.setVisibility(0);               
        }
	}
	public void myClickHandler(View view){
		Intent myIntent2 = new Intent(getApplicationContext(),TaskSchedulerActivity.class);
		switch(view.getId()){
		case R.id.notaskbutton1:
				startActivity(myIntent2);
			break;
		case R.id.tasklistbutton1:
				startActivity(myIntent2);
			break;
		}
	}
	public void onResume(){
		super.onResume();
		refershList();    
	}
		
	public void onPause(){
		super.onPause();
		//this.dh.openHelper.close();
	}
	protected void onListItemClick(ListView l, View v, int position, long id) {	
		String str1=null;
		String str2=null;
		String str3=null;
		String str4=null;
		String columns[] = new String[] {"task", "start", "end", "time"};
		Uri taskuri = Uri.parse("content://com.taskscheduler.provider.Task/tasks/"+(position+1));
		ContentResolver cr = getContentResolver();

		Cursor c = cr.query(taskuri, columns, null, null, null );

		//Cursor c = managedQuery(taskuri, columns, null, null, null);		
		if(c.moveToFirst()){
			// Get the field values
			str1 = c.getString(c.getColumnIndex("task"));
			str2 = c.getString(c.getColumnIndex("start"));
			str3 = c.getString(c.getColumnIndex("end"));
			str4 = c.getString(c.getColumnIndex("time"));			
			
		}		
		str2 = str2.replace("00:00:00 GMT+05:30 ", "");
		str3 = str3.replace("00:00:00 GMT+05:30 ", "");
		Toast.makeText(TaskListActivity.this, "Task: "+str1+"\nStart date: "+str2+"\nStart time: "+str4+"\nDue date: "+str3+"\n", Toast.LENGTH_LONG).show();
		if (c != null && !c.isClosed()){
            c.close();
        }					
	}
}

This component actually works together with TaskSchedulerContentProvider.java and create the task for the app.

Listing 16: All the packages & class declaration is here.

TaskSchedulerActivity.java
package com.example.taskscheduler;

import java.util.Calendar;
import java.util.Date;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.TimePicker;
import android.widget.Toast;

public class TaskSchedulerActivity extends Activity {
	/** Called when the activity is first created. */

	private DatePicker sDate;
	private DatePicker dDate;
	private TimePicker sTime;
	private EditText mNotes;
	private CheckBox mAlarm;
	boolean FLAG = true;

Listing 17: The on create method, to bind the data with the control property as soon as one task creation is done

@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		sDate = (DatePicker) findViewById(R.id.mdatePicker1);
		dDate = (DatePicker) findViewById(R.id.mdatePicker2);
		sTime = (TimePicker) findViewById(R.id.mtimePicker1);
		mNotes = (EditText) findViewById(R.id.meditText1);
		mAlarm = (CheckBox) findViewById(R.id.mcheckBox1);		
	}

	public void myClickHandler(View view) {
		try {
			switch (view.getId()) {
			case R.id.mbutton1: {
				save();
				break;
			}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			Log.e("Error", e.toString());
		}
	}

Listing 18: it generates the task date and the save / store the same permanently to the storage

public void save() {
		
		int st_day = sDate.getDayOfMonth();
		int st_mon = sDate.getMonth();
		int st_year = sDate.getYear();
		
		Date st_date = new Date (st_year-1900, st_mon, st_day);
		
		//mNotes.setText ("Start date: " + st_date.toString());
		
		int due_day = dDate.getDayOfMonth();
		int due_mon = dDate.getMonth();
		int due_year = dDate.getYear();

		Date due_date = new Date (due_year-1900, due_mon, due_day);
		//mNotes.setText (mNotes.getText() + "\nDue date: " + due_date.toString());
		
		int st_hour = sTime.getCurrentHour();
		int st_min = sTime.getCurrentMinute();
		
		String task = String.valueOf(mNotes.getText());
		Boolean alarm = mAlarm.isChecked();
		
		Date curr_date = new java.util.Date();
		int curr_hour = curr_date.getHours();
		int curr_min = curr_date.getMinutes();
		
		Calendar cal = Calendar.getInstance();       // get calendar instance 
		cal.setTime(curr_date);                           // set cal to date 
		cal.set(Calendar.HOUR_OF_DAY, 0);            // set hour to midnight 
		cal.set(Calendar.MINUTE, 0);                 // set minute in hour 
		cal.set(Calendar.SECOND, 0);                 // set second in minute 
		cal.set(Calendar.MILLISECOND, 0);            // set millis in second 
		curr_date = cal.getTime();             		// actually computes the new Date 
		
		
		//mNotes.setText (mNotes.getText() + "\nCurr date: " + curr_date.toString());

		if (st_date.compareTo(curr_date)<0)
		{
			Toast.makeText(TaskSchedulerActivity.this,"Start date/time cannot be less than the current date/time.", Toast.LENGTH_SHORT).show();
			return;
		}
		else if (st_date.compareTo(curr_date)== 0)
		{
			if (st_hour < curr_hour)
			{
				Toast.makeText(TaskSchedulerActivity.this,"Start date/time cannot be less than the current date/time.", Toast.LENGTH_SHORT).show();
				return;
			}
			else if ((st_hour == curr_hour) && (st_min < curr_min))
			{
				Toast.makeText(TaskSchedulerActivity.this,"Start date/time cannot be less than the current date/time.", Toast.LENGTH_SHORT).show();
				return;
			}
		}
		if (due_date.compareTo(st_date) < 0)
			Toast.makeText(TaskSchedulerActivity.this,"Due date cannot be less than start date.", Toast.LENGTH_SHORT).show();
		else if (task.equalsIgnoreCase(""))
			Toast.makeText(TaskSchedulerActivity.this,"The Task field cannot be left blank.", Toast.LENGTH_SHORT).show();
		else{
			ContentValues values = new ContentValues();
			values.put("key", task+""+st_date.toString()+""+String.valueOf(st_hour)+":"+String.valueOf(st_min));
			values.put("task", task);
			values.put("start", st_date.toString());
			values.put("end", due_date.toString());
			values.put("time", String.valueOf(st_hour)+":"+String.valueOf(st_min));
			values.put("alarm", alarm.toString());
			getContentResolver().insert(Uri.parse("content://com.taskscheduler.provider.Task/tasks"), values);
			Intent intent = new Intent(this, ExpireTimeService.class); 
	        stopService(intent);
	    	startService(intent);
			finish();
		}
	}
}

This is how the entire Task creation app is getting created, you can use this app for your personal usage also by installing it form the Google Play(if you have a developer account there).

Happy Development



Certified Trainer for Windows 8 Mobile App Development, IBM CE Project Trainer With IBM DB2, RAD, RSA, Certified Trainer for ZEND (PHP), Certified PHP and MySql trainer, Certified trainer of Diploma in Oracle 10g (DBA) as per Orac...

What did you think of this post?
Services
[Close]
To have full access to this post (or download the associated files) you must have MrBool Credits.

  See the prices for this post in Mr.Bool Credits System below:

Individually – in this case the price for this post is US$ 0,00 (Buy it now)
in this case you will buy only this video by paying the full price with no discount.

Package of 10 credits - in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download few videos. In this plan you will receive a discount of 50% in each video. Subscribe for this package!

Package of 50 credits – in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download several videos. In this plan you will receive a discount of 83% in each video. Subscribe for this package!


> More info about MrBool Credits
[Close]
You must be logged to download.

Click here to login