Index: conduit/dataproviders/DataProvider.py
===================================================================
--- conduit/dataproviders/DataProvider.py	(revision 1364)
+++ conduit/dataproviders/DataProvider.py	(working copy)
@@ -495,6 +495,13 @@
     def __init__(self, **kwargs):
         gobject.GObject.__init__(self)
 
+    def emit(self, *args):
+        """
+        Override the gobject signal emission so that all signals are emitted 
+        from the main loop on an idle handler
+        """
+        gobject.idle_add(gobject.GObject.emit,self,*args)
+
     def emit_added(self, klass, initargs=(), category=None):
         if category == None:
             category = getattr(klass, "_category_", conduit.dataproviders.CATEGORY_TEST)
Index: conduit/modules/PhoneModule/__init__.py
===================================================================
--- conduit/modules/PhoneModule/__init__.py	(revision 0)
+++ conduit/modules/PhoneModule/__init__.py	(revision 0)
@@ -0,0 +1 @@
+
Index: conduit/modules/PhoneModule/PhoneModule.py
===================================================================
--- conduit/modules/PhoneModule/PhoneModule.py	(revision 0)
+++ conduit/modules/PhoneModule/PhoneModule.py	(revision 0)
@@ -0,0 +1,84 @@
+import logging
+log = logging.getLogger("modules.Phone")
+
+import gobject
+import conduit
+import conduit.dataproviders.DataProvider as DataProvider
+import conduit.dataproviders.DataProviderCategory as DataProviderCategory
+import conduit.Utils as Utils
+
+Utils.dataprovider_add_dir_to_path(__file__, "")
+import ScanThreads
+
+MODULES = {
+    "PhoneFactory" : { "type": "dataprovider-factory" },
+}
+
+class PhoneFactory(DataProvider.DataProviderFactory):
+    """ 
+    Looks for phones connected via bluetooth
+    """
+    def __init__(self, **kwargs):
+        DataProvider.DataProviderFactory.__init__(self, **kwargs)
+        self._cats = {}
+        self._phones = {}
+
+        #Scan multiple interfaces at once
+        self.threads = (
+            ScanThreads.Bluetooth(self._found_phone_callback),
+        )
+
+    def _found_phone_callback(self, address, name, driver):
+        #Get/create the named phone category
+        if name not in self._cats:
+            self._cats[name] = DataProviderCategory.DataProviderCategory(
+                                    name,
+                                    "phone",
+                                    address)    
+        category = self._cats[name]
+
+        #create the klass for controlling the phone
+        if True:
+            klass = Test
+        elif driver == "gammu":
+            klass = GammuDataProvider
+        elif driver == "obex":
+            klass = ObexFileDataProvider
+
+        self.emit_added(
+                    klass,
+                    (address,),
+                    category
+                    ) 
+
+    def probe(self):
+        log.info("Starting Scan Threads")
+        for t in self.threads:
+            t.start()
+
+    def quit(self):
+        log.info("Stopping Scan Threads")
+        for t in self.threads:
+            t.cancel()
+
+class GammuDataProvider:
+    pass
+
+class ObexFileDataProvider:
+    pass
+
+class Test(DataProvider.DataSource):
+
+    _name_ = "Test Phone"
+    _description_ = "Test Phone"
+    _module_type_ = "source"
+    _in_type_ = "contact"
+    _out_type_ = "contact"
+    _icon_ = "dialog-warning"
+
+    def __init__(self, *args):
+        DataProvider.DataSource.__init__(self)
+
+    def get_UID(self):
+        return ""
+
Index: conduit/modules/PhoneModule/ScanThreads.py
===================================================================
--- conduit/modules/PhoneModule/ScanThreads.py	(revision 0)
+++ conduit/modules/PhoneModule/ScanThreads.py	(revision 0)
@@ -0,0 +1,102 @@
+import logging
+log = logging.getLogger("modules.Phone")
+
+import thread
+
+import threading
+import bluetooth
+import gammu
+import time
+
+import conduit
+
+class _ScanThread(threading.Thread):
+    SLEEP_TIME = 20
+    SLEEP = 0.1
+    def __init__(self):
+        threading.Thread.__init__(self)
+        self._found = {}
+        self._cancelled = False
+        self._foundLock = threading.Lock()
+
+    def found_phone(self, address, name):
+        self._foundLock.acquire()
+        if address not in self._found:
+            log.info("Thread %s Found Phone: %s" % (thread.get_ident(),address))
+            self._found[address] = name
+        self._foundLock.release()
+
+    def pause_scanning(self):
+        i = 0
+        while ( i < (self.SLEEP_TIME/self.SLEEP) ) and ( self.is_cancelled() == False ):
+            time.sleep(self.SLEEP)
+            i += 1
+
+    def is_cancelled(self):
+        return conduit.GLOBALS.cancelled or self._cancelled
+
+    def cancel(self):
+        self._cancelled = True
+
+class _DeviceDiscovererFilter(bluetooth.DeviceDiscoverer):
+    def __init__(self, parent):
+        bluetooth.DeviceDiscoverer.__init__(self)
+        self.parent = parent
+
+    def device_discovered(self, address, device_class, name):
+        '''
+        Called when device is iscovered, checks device is phone
+        '''
+        log.info("Bluetooth Device Discovered: %s@%s" % (name,address))
+        major_class = ( device_class & 0xf00 ) >> 8
+        # We want only devices with phone class
+        # See https://www.bluetooth.org/apps/content/?doc_id=49706
+        if major_class == 2:
+            self.parent.found_phone(address, name)
+
+    def inquiry_complete(self):
+        log.debug("Bluetooth Search Complete")
+
+class Bluetooth(_ScanThread):
+    def __init__(self, foundCallback):
+        _ScanThread.__init__(self)
+        self.foundCallback = foundCallback
+        self.discovery = _DeviceDiscovererFilter(self)
+
+    def run(self):
+        while self.is_cancelled() == False:
+            log.info("Beginning Bluetooth Scan")
+            self.discovery.find_devices()
+            self.discovery.process_inquiry()
+            for a,name in self._found.items():
+                log.info("Thread %s Getting info on %s"% (thread.get_ident(),a))
+                try:
+                    sm = gammu.StateMachine()
+                    sm.SetConfig(0,
+                            {'StartInfo': 'no',
+                             'UseGlobalDebugFile': 1,
+                             'DebugFile': '',
+                             'SyncTime': 'no',
+                             'Connection': 'blueat',
+                             'LockDevice': 'no',
+                             'DebugLevel': 'nothing',
+                             'Device': a,
+                             'Localize': None,
+                             'Model': '',
+                             })
+                    sm.Init()
+                    
+                    gammuModel, phoneModel = sm.GetModel()
+                    log.info("GOT PHONE: %s" % phoneModel)
+
+                    self.foundCallback(a,name,"gammu")
+
+                except gammu.GSMError, val:
+                    info = val[0]
+                    log.warn("Failed to connect to phone: %s"% info)
+
+            self.pause_scanning()
+
+    def cancel(self):
+        _ScanThread.cancel(self)
+        self.discovery.cancel_inquiry()
