=== modified file 'apport/crashdb_impl/launchpad.py'
--- apport/crashdb_impl/launchpad.py	2019-07-22 21:23:40 +0000
+++ apport/crashdb_impl/launchpad.py	2020-06-23 15:09:13 +0000
@@ -239,7 +239,10 @@
 
         hostname = self.get_hostname()
 
-        project = self.options.get('project')
+        if self.options.get('project'):
+            project = self.options.get('project')
+        elif 'SnapSource' in report:
+            project = report['SnapSource']
 
         if not project:
             if 'SourcePackage' in report:

=== modified file 'apport/fileutils.py'
--- apport/fileutils.py	2019-12-03 03:18:16 +0000
+++ apport/fileutils.py	2020-06-23 15:09:13 +0000
@@ -9,7 +9,7 @@
 # option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
 # the full text of the license.
 
-import os, glob, subprocess, os.path, time, pwd, sys
+import os, glob, subprocess, os.path, time, pwd, sys, requests_unixsocket
 
 try:
     from configparser import ConfigParser, NoOptionError, NoSectionError
@@ -104,6 +104,21 @@
     return packaging.get_file_package(file)
 
 
+def find_snap(snap):
+    '''Return the data of the given snap.
+
+    Return None if the snap is not found to be installed.
+    '''
+    session = requests_unixsocket.Session()
+    try:
+        r = session.get('http+unix://%2Frun%2Fsnapd.socket/v2/snaps/{}'.format(snap))
+        if r.status_code == 200:
+            j = r.json()
+            return j["result"]
+    except Exception:
+        return None
+
+
 def seen_report(report):
     '''Check whether the report file has already been processed earlier.'''
 

=== modified file 'apport/report.py'
--- apport/report.py	2020-06-04 00:08:18 +0000
+++ apport/report.py	2020-06-23 15:09:13 +0000
@@ -19,10 +19,12 @@
     _python2 = False
     from urllib.error import URLError
     from urllib.request import urlopen
+    from urllib.parse import unquote
     (urlopen, URLError)  # pyflakes
 else:
     _python2 = True
     from urllib import urlopen
+    from urllib import unquote
     URLError = IOError
 
 import problem_report
@@ -351,7 +353,7 @@
             try:
                 self['SourcePackage'] = packaging.get_source(package)
             except ValueError:
-                # might not exist for non-free third-party packages
+                # might not exist for non-free third-party packages or snaps
                 pass
         if not version:
             return
@@ -376,6 +378,26 @@
             self['Dependencies'] += '%s %s%s' % (
                 dep, v, self._customized_package_suffix(dep))
 
+    def add_snap_info(self, snap):
+        '''Add info about an installed Snap
+
+        This adds a Snap: field, containing name, version and channel.
+        It adds a SnapSource: field, if the snap has a Launchpad contact defined.
+        '''
+        self['Snap'] = '%s %s (%s)' % (snap.get("name"), snap.get("version"),
+                                        snap.get("channel", "unknown"))
+        # Automatically handle snaps which have a Launchpad contact defined
+        if snap.get("contact"):
+            # Parse Launchpad project (e.g. 'subiquity') or source package string
+            # (e.g. 'ubuntu/+source/gnome-calculator') from snap 'contact'
+            # Additionaly, extract any tag/tags defined in the contact URL.
+            p = r'^https?:\/\/.*launchpad\.net\/((?:[^\/]+\/\+source\/)?[^\/]+)(?:.*field\.tags?=([^&]+))?'
+            m = re.search(p, unquote(snap.get("contact", "")))
+            if m and m.group(1):
+                self['SnapSource'] = m.group(1)
+                if m.group(2):
+                    self['SnapTags'] = m.group(2)
+
     def add_os_info(self):
         '''Add operating system information.
 

=== modified file 'apport/ui.py'
--- apport/ui.py	2020-03-09 22:18:03 +0000
+++ apport/ui.py	2020-06-23 15:09:13 +0000
@@ -66,19 +66,6 @@
     return True
 
 
-def find_snap(package):
-    import requests_unixsocket
-
-    session = requests_unixsocket.Session()
-    try:
-        r = session.get('http+unix://%2Frun%2Fsnapd.socket/v2/snaps/{}'.format(package))
-        if r.status_code == 200:
-            j = r.json()
-            return j["result"]
-    except Exception:
-        return None
-
-
 def thread_collect_info(report, reportfile, package, ui, symptom_script=None,
                         ignore_uninstalled=False):
     '''Collect information about report.
@@ -122,12 +109,20 @@
             package = apport.fileutils.find_file_package(report['ExecutablePath'])
         else:
             raise KeyError('called without a package, and report does not have ExecutablePath')
+
+    # check if the package name relates to an installed snap
+    snap = apport.fileutils.find_snap(package)
+    if snap:
+        report.add_snap_info(snap)
+
     try:
         report.add_package_info(package)
     except ValueError:
         # this happens if we are collecting information on an uninstalled
         # package
-        if not ignore_uninstalled:
+
+        # we found no package, but a snap, so lets continue
+        if not ignore_uninstalled and 'Snap' not in report:
             raise
     except SystemError as e:
         report['UnreportableReason'] = excstr(e)
@@ -140,7 +135,7 @@
         # the chance to set a third-party CrashDB.
         try:
             if 'CrashDB' not in report and 'APPORT_DISABLE_DISTRO_CHECK' not in os.environ:
-                if 'Package' not in report:
+                if 'Package' not in report and 'Snap' not in report:
                     report['UnreportableReason'] = _('This package does not seem to be installed correctly')
                 elif not apport.packaging.is_distro_package(report['Package'].split()[0]) and  \
                         not apport.packaging.is_native_origin_package(report['Package'].split()[0]):
@@ -150,7 +145,9 @@
         except ValueError:
             # this happens if we are collecting information on an uninstalled
             # package
-            if not ignore_uninstalled:
+
+            # we found no package, but a snap, so lets continue
+            if not ignore_uninstalled and 'Snap' not in report:
                 raise
 
     # add title
@@ -858,7 +855,7 @@
         elif '/' in self.args[0]:
             if self.args[0].startswith('/snap/bin'):
                 # see if the snap has the same name as the executable
-                snap = find_snap(self.args[0].split('/')[-1])
+                snap = apport.fileutils.find_snap(self.args[0].split('/')[-1])
                 if not snap:
                     optparser.error('%s is provided by a snap. No contact address has been provided; visit the forum at https://forum.snapcraft.io/ for help.' % self.args[0])
                 elif snap.get("contact", ""):
@@ -1134,16 +1131,6 @@
                         repr(e))
                     self.report['_MarkForUpload'] = 'False'
                 except ValueError:  # package does not exist
-                    snap = find_snap(self.cur_package)
-                    if not snap:
-                        pass
-                    elif snap.get("contact", ""):
-                        self.report['UnreportableReason'] = _('This report is about a snap published by %s. Contact them via %s for help.') % (snap["developer"], snap["contact"])
-                        self.report['_MarkForUpload'] = 'False'
-                    else:
-                        self.report['UnreportableReason'] = _('This report is about a snap published by %s. No contact address has been provided; visit the forum at https://forum.snapcraft.io/ for help.') % snap["developer"]
-                        self.report['_MarkForUpload'] = 'False'
-
                     if 'UnreportableReason' not in self.report:
                         self.report['UnreportableReason'] = _('This report is about a package that is not installed.')
                         self.report['_MarkForUpload'] = 'False'
@@ -1153,18 +1140,52 @@
                                                           'process this problem report:') + '\n\n' + str(e)
                     self.report['_MarkForUpload'] = 'False'
 
-            snap = find_snap(self.cur_package)
-            if snap and 'UnreportableReason' not in self.report and self.specified_a_pkg:
-                if snap.get("contact", ""):
-                    msg = _('You are about to report a bug against the deb package, but you also a have snap published by %s installed. You can contact them via %s for help. Do you want to continue with the bug report against the deb?') % (snap["developer"], snap["contact"])
-                else:
-                    msg = _('You are about to report a bug against the deb package, but you also a have snap published by %s installed. For the snap, no contact address has been provided; visit the forum at https://forum.snapcraft.io/ for help. Do you want to continue with the bug report against the deb?') % snap["developer"]
-
-                if not self.ui_question_yesno(msg):
+            # ask for target, if snap and deb package are installed
+            if 'Snap' in self.report and 'Package' in self.report:
+                if '(not installed)' in self.report['Package']:
+                    # choose snap automatically, if deb package is not installed
+                    res = [0]
+                else:
+                    res = self.ui_question_choice(_('You have two versions of this application installed, which one do you want to report a bug against?'), [
+                                                  _('%s snap') % self.report['Snap'],
+                                                  _('%s deb package') % self.report['Package']
+                                                  ], False)
+                # bug report is about the snap, clean deb package info
+                if res == [0]:
+                    del self.report['Package']
+                    if 'PackageArchitecture' in self.report:
+                        del self.report['PackageArchitecture']
+                    if 'SourcePackage' in self.report:
+                        del self.report['SourcePackage']
+                # bug report is about the deb package, clean snap info
+                elif res == [1]:
+                    del self.report['Snap']
+                    if 'SnapSource' in self.report:
+                        del self.report['SnapSource']
+                    if 'SnapTags' in self.report:
+                        del self.report['SnapTags']
+                else:
                     self.ui_stop_info_collection_progress()
                     sys.exit(0)
                     return
 
+            # append snap tags, if this report is about the snap
+            if 'Snap' in self.report and 'SnapTags' in self.report:
+                current_tags = self.report.get('Tags', '')
+                if current_tags:
+                    current_tags += ' '
+                self.report['Tags'] = current_tags + self.report['SnapTags']
+                del self.report['SnapTags']
+
+            # show a hint if we cannot auto report a snap bug via 'SnapSource'
+            if 'Snap' in self.report and 'SnapSource' not in self.report and 'UnreportableReason' not in self.report and self.specified_a_pkg:
+                snap = apport.fileutils.find_snap(self.cur_package)
+                if snap.get("contact", ""):
+                    self.report['UnreportableReason'] = _('%s is provided by a snap published by %s. Contact them via %s for help.') % (snap["name"], snap["developer"], snap["contact"])
+                else:
+                    self.report['UnreportableReason'] = _('%s is provided by a snap published by %s. No contact address has been provided; visit the forum at https://forum.snapcraft.io/ for help.') % (snap["name"], snap["developer"])
+                self.report['_MarkForUpload'] = 'False'
+
             if 'UnreportableReason' in self.report or not self.check_report_crashdb():
                 self.ui_stop_info_collection_progress()
                 if on_finished:
@@ -1227,9 +1248,10 @@
 
             # check that we were able to determine package names
             if 'UnreportableReason' not in self.report:
-                if (('SourcePackage' not in self.report and 'Dependencies' not in self.report) or
+                if ((('SourcePackage' not in self.report and 'Dependencies' not in self.report) or
                     (not self.report.get('ProblemType', '').startswith('Kernel') and
-                     'Package' not in self.report)):
+                    'Package' not in self.report)) and ('SnapSource' not in self.report and
+                    'Snap' not in self.report)):
                     self.ui_error_message(_('Invalid problem report'),
                                           _('Could not determine the package or source package name.'))
                     # TODO This is not called consistently, is it really needed?

=== modified file 'debian/changelog'
--- debian/changelog	2020-06-04 00:08:18 +0000
+++ debian/changelog	2020-06-23 15:09:13 +0000
@@ -1,3 +1,10 @@
+apport (2.20.11-0ubuntu27.4) focal; urgency=medium
+
+  * Backport snap handling for ubuntu-bug from Groovy (LP: #1861082)
+  * Build-depend on python3-requests-unixsocket.
+
+ -- Lukas Märdian <lukas.maerdian@canonical.com>  Tue, 23 Jun 2020 15:15:30 +0200
+
 apport (2.20.11-0ubuntu27.3) focal; urgency=medium
 
   * apport_python_hook.py: if python apt modules are not built for the python

=== modified file 'debian/control'
--- debian/control	2020-02-21 20:47:17 +0000
+++ debian/control	2020-06-23 15:09:13 +0000
@@ -12,6 +12,7 @@
  python3-all
 Build-Depends-Indep: python3-distutils-extra (>= 2.24~),
  python3-apt (>= 0.7.9),
+ python3-requests-unixsocket,
  dh-python,
  intltool,
  xvfb,

