Automatic navigation to next calendar event on Bluetooth connection

Recently there ware a question on Reddit asking if it is possible to automatically navigate to the next appointment when connecting to car's bluetooth.

Well, what exactly would this task entail?

  1. Reading events from Calendar
  2. Being able to determine if next meeting is within preconfigured timeframe
  3. Displaying a list of meetings if more than 1 meeting fits criteria
  4. Ability to send intents to navigation software with a location

Based on this list, we could have 2 problems: Reading calendar and sending intents. Luckily both are possible (kind of). Reading calendar requires root.

1 - Reading Events from Calendar

Since Calendar database is a protected file, we need to open this file with root privileges.

Thanks to this thread, I found a basic query to get list of calendar events, which looks like this:

SELECT BEGIN, calendar_displayname, title
       FROM Instances, Events, Calendars WHERE Instances.event_id = Events._id AND 
       DATE(DATETIME(BEGIN / 1000 , 'unixepoch')) = DATE('now') AND Events.Calendar_id = Calendars._id 
       ORDER BY Instances.BEGIN ASC;

Since we need only events that have location added, this query has to be modified like so:

SELECT DISTINCT BEGIN, calendar_displayname, title, eventLocation 
       FROM Instances, Events, Calendars WHERE Instances.event_id = Events._id AND 
       DATE(DATETIME(BEGIN / 1000 , 'unixepoch')) = DATE('now') AND Events.Calendar_id = Calendars._id AND
       eventLocation <> ''
       ORDER BY Instances.BEGIN ASC;

2 - Getting Events Within a Time Window

Since it most likely does not make sense to navigate to a meeting that happens 12 hours from now, we need to modify our query only to those meetings within a preconfigured time frame. For that, we will need to create a variable in Tasker, call it %timediff and set it to, let's say 2 hours. Now our query will look something like this:

SELECT DISTINCT BEGIN / 1000 , title, eventLocation 
       FROM Instances, Events, Calendars WHERE Instances.event_id = Events._id AND 
       DATE(DATETIME(BEGIN / 1000 , 'unixepoch')) = DATE('now') AND Events.Calendar_id = Calendars._id AND 
       eventLocation <> '' AND (BEGIN - %TIMEMS) > 0 AND (BEGIN / 1000 - %TIMES) <= (%TIMEDIFF * 3600) 
       ORDER BY Instances.BEGIN ASC;

Where:

  • %TIMEMS is current time in milliseconds
  • %TIMES is current time in seconds

The result of this query is going to be in the form of:

1407508760 | Write a blog post | 123 some address

3 - Display List of Matched Meetings

Once we get our resulting meeting set, we need to present it to the user. Luckily Tasker makes it very easy with notion of scenes. There is one scene element in particular that makes this task very trivial. It is called Menu. Here are steps to get you up and running:

  1. Create a new scene
  2. Add a Text Field for use as a title bar
  3. Add a Menu Element and configure it:
    1. Source: Variable Array
    2. Variable: %CAL_EVENTS
    3. Selection Mode: Single
    4. Optionally, modify Item Layout
    5. Under Item Tap, add a new action: Destroy Scene
  4. Arrange your scene elements so that it looks like a dialog box: Title bar on top and menu list on the bottom

4 - Start Navigation via Intent

Now the final piece of the puzzle: sending intent to navigation software to start routing to the location of the meeting.

For this tutorial, I am going to focus on 2 programs: Google Navigator and Waze.

For Google Navigator, use this:

Action: android.intent.action.VIEW
Category: None
Type:
Data: google.navigation:q=final+destination+address
Extra:
Package: com.google.android.apps.maps
Class: com.google.android.maps.MapsActivity
Target: Activity

Waze looks very similarly:

Action: android.intent.action.VIEW
Category: None
Type:
Data: waze://?q=final+destination+address&navigate=yes
Extra:
Package:
Class:
Target: Activity

Of course in our profile, we are going to replace final+destination+address with a variable

Putting it all together in a Task and Scene

So, now that you understand the tricky parts, here's the exported description for both the Task and the Scene (XML Exports are attached at the end):

Task Export:

Test Auto Navigation (73)
A1: Variable Set [ Name:%sqlite3 To:/data/data/com.keramidas.TitaniumBackup/files/sqlite3 Do Maths:Off Append:Off ] 
A2: Variable Set [ Name:%timediff To:2 Do Maths:Off Append:Off ] 
A3: Array Clear [ Name:%locations ] 
A4: Array Clear [ Name:%CAL_EVENTS ] 
A5: Run Shell [ Command:%sqlite3 /data/data/com.android.providers.calendar/databases/calendar.db "SELECT DISTINCT begin / 1000 , title, eventLocation FROM Instances, Events, Calendars WHERE Instances.event_id = Events._id AND date(datetime(begin / 1000 , 'unixepoch')) = date('now') AND Events.Calendar_id = Calendars._id AND eventLocation <> '' AND (begin - %TIMEMS) > 0 AND (begin / 1000 - %TIMES) <= (%timediff * 3600) ORDER BY Instances.begin ASC;" Timeout (Seconds):0 Use Root:On Store Output In:%calendar_events Store Errors In: Store Result In: ] 
A6: Variable Search Replace [ Variable:%calendar_events Search:\n Ignore Case:Off Multi-Line:Off One Match Only:Off Store Matches In: Replace Matches:On Replace With:// ] If [ %calendar_events Set ]
A7: Variable Split [ Name:%calendar_events Splitter:// Delete Base:Off ] If [ %calendar_events Set ]
A8: For [ Variable:%item Items:%calendar_events() ] If [ %calendar_events Set ]
A9: Variable Split [ Name:%item Splitter:| Delete Base:Off ] 
A10: Array Push [ Name:%CAL_EVENTS Position:99999 Value:%item(2) @ %item(3) Fill Spaces:Off ] 
A11: Variable Search Replace [ Variable:%item(3) Search:  Ignore Case:Off Multi-Line:Off One Match Only:Off Store Matches In: Replace Matches:On Replace With:+ ] 
A12: Array Push [ Name:%locations Position:99999 Value:%item(3) Fill Spaces:Off ] 
A13: End For 
A14: Popup [ Title:No Events... Text:... found within %timediff hour(s) Background Image: Layout:Popup Timeout (Seconds):5 Show Over Keyguard:On ] If [ %CAL_EVENTS(#) = 0 ]
A15: Show Scene [ Name:Destination List Display As:Dialog Horizontal Position:100 Vertical Position:100 Animation:System Show Exit Button:Off Continue Task Immediately:Off ] If [ %CAL_EVENTS(#) > 0 ]
A16: Send Intent [ Action:android.intent.action.VIEW Cat:None Mime Type: Data:google.navigation:q=%locations(%tap_index) Extra: Extra: Package:com.google.android.apps.maps Class:com.google.android.maps.MapsActivity Target:Activity ] If [ %tap_index > 0 ]
A17: [X] Send Intent [ Action:android.intent.action.VIEW Cat:None Mime Type: Data:waze://?q=%locations(%tap_index)&navigate=yes Extra: Extra: Package: Class: Target:Activity ] If [ %tap_index > 0 ]

Scene Export:

Scene: Destination List
P:649x525 L:-1x-1
 
Orientation: System
Background Colour: #FF0E0E0E
Action Bar Style: System
Title: Destination List
Subtitle: 
Icon: null
Tab Labels:
 
Element: Menu/Menu
Geometry:
P:0,176 649x349 L:-1,-1 -1x-1
Content:
Source: Variable Array
Variable: %CAL_EVENTS
Selection Mode: Single
Item Layout: Builtin Item Layout
Horizontal Space: 3
Vertical Space: 3
Events:
ItemClick: 68
 
Element: Text1/Text
Geometry:
P:0,0 644x158 L:-1,-1 -1x-1
Content:
Text: Destination Choices
Text Size: 20
Text Width Scale Percent: 115
Text Colour: #FFFFFFFF
Font: 
Position: Centre
Vertical Fit Mode: None
Text Format: Plain Text

Couple of things to note regarding the Task:

  1. Change value of A1 to a valid path to your sqlite3 executable. If you don't know where it is at on your system, run: adb shell find / -name sqlite3 on your computer with your phone connected
  2. Change value of A2 to a value of your choice. Please remember that this value is in hours!
  3. Configure your navigation preference
  • Google Navigator: Enable A16 and disable A17
  • Waze: Enable A17 and disable A16

Once you've got your Task and the Scene in your Tasker, create a simple profile that will activate on Bluetooth connection and execute task above!

Profile Dependencies: 
Root Access
AttachmentSize
File XML Export of the Scene3.9 KB
File XML Export of the Task4.97 KB

Comments