<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def get_product_info(self, apps=[], packages=[], timeout=15):
 """Get product info for apps and packages :param apps: items in the list should be either just ``app_id``, or ``(app_id, access_token)`` :type apps: :class:`list` :param packages: items in the list should be either just ``package_id``, or ``(package_id, access_token)`` :type packages: :class:`list` :return: dict with ``apps`` and ``packages`` containing their info, see example below :rtype: :class:`dict`, :class:`None` .. code:: python } """

        if not apps and not packages: return

        message = MsgProto(EMsg.ClientPICSProductInfoRequest)

        for app in apps:
                app_info = message.body.apps.add()
                app_info.only_public = False
                if isinstance(app, tuple):
                        app_info.appid, app_info.access_token = app
                else:
                        app_info.appid = app

        for package in packages:
                package_info = message.body.packages.add()
                if isinstance(package, tuple):
                        package_info.appid, package_info.access_token = package
                else:
                        package_info.packageid = package

        message.body.meta_data_only = False

        job_id = self.send_job(message)

        data = dict(apps={}, packages={})

        while True:
            chunk = self.wait_event(job_id, timeout=timeout)

            if chunk is None: return
            chunk = chunk[0].body

            for app in chunk.apps:
                data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode('utf-8', 'replace'))['appinfo']
            for pkg in chunk.packages:
                data['packages'][pkg.packageid] = vdf.binary_loads(pkg.buffer[4:])[str(pkg.packageid)]

            if not chunk.response_pending:
                break

        return data