Bug 23284

Summary: Add support for ContactCapabilities interface
Product: Telepathy Reporter: Siraj Razick <siraj>
Component: tp-qtAssignee: Telepathy bugs list <telepathy-bugs>
Status: RESOLVED FIXED QA Contact: Telepathy bugs list <telepathy-bugs>
Severity: normal    
Priority: medium    
Version: unspecified   
Hardware: Other   
OS: All   
URL: http://git.collabora.co.uk/?p=user/andrunko/telepathy-qt4.git;a=shortlog;h=refs/heads/capabilities
Whiteboard:
i915 platform: i915 features:
Bug Depends on: 22934    
Bug Blocks: 22910    

Description Siraj Razick 2009-08-12 21:21:39 UTC
There is no way to access ContactCapabilities through account, since the interface class is not auto generated.
Comment 1 Will Thompson 2009-08-13 02:42:28 UTC
The bindings aren't generated because the ContactCapabilities interface is still a draft and subject to change.
Comment 2 Simon McVittie 2009-10-06 07:55:01 UTC
Hijacking this bug for the high-level API...
Comment 3 Simon McVittie 2009-10-06 08:14:48 UTC
My most serious objections are:

We don't seem to provide any way to distinguish between "capabilities not yet known" and "known to have no capabilities at all" (in the D-Bus API that's omission from the mapping vs. an empty list). That probably requires use of contains() when doing the setup? Perhaps we could have isEmpty() (true for both unknown and no-caps) and isUnknown() (only true for unknown)?

We can return a null pointer for peoples' capabilities (if the feature isn't ready, and perhaps also when they're really not known), which in practice seems very likely to crash UIs; when we support distinguishing between "caps not known" and "no caps at all", perhaps we could have a global singleton for "caps not known" and return a const pointer to that?

Every UI is required to implement the failover from contact capabilities to connection capabilities by itself, rather than us doing that automatically (with a way for advanced UIs to detect when it's happened) as I suggested at the design stage. Are you *absolutely* sure that your way is better? Why?

If so, it should be explicitly documented when to do that (isUnknown() returns true) and what you should probably do in response (get the connection capabilities).

----

Less serious:

As already noted on IRC, we should have a configure check for <http://qt.gitorious.org/qt/qt/merge_requests/1657>, and class_ is ugly (I prefer cls).

+bool CapabilitiesBase::supportsUpgradingCalls() const
+{
+    QString channelType;
+    foreach (const RequestableChannelClass &class_, mPriv->classes) {
+        channelType = qdbus_cast<QString>(class_.fixedProperties.value(
+                QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")));
+        if (channelType == TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA &&
+            !class_.allowedProperties.contains(
+                QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".ImmutableStreams"))) {
+                // TODO should we test all classes that have channelType
+                //      StreamedMedia or just one is fine?

I think in practice, if one channel class has immutable streams, they all will, so this is OK. Alter the comment to say so.

"Added CapabilitiesBase to build system." shouldn't be a separate patch; don't change it now, but in future, patches that add new source files should add them to the build at the same time.

Do we actually have ensureVideoCall etc. yet? We probably should, if the docs are going to reference them :-)
Comment 4 Andre Moreira Magalhaes 2009-10-06 09:15:47 UTC
(In reply to comment #3)
> My most serious objections are:
> 
> We don't seem to provide any way to distinguish between "capabilities not yet
> known" and "known to have no capabilities at all" (in the D-Bus API that's
> omission from the mapping vs. an empty list). That probably requires use of
> contains() when doing the setup? Perhaps we could have isEmpty() (true for both
> unknown and no-caps) and isUnknown() (only true for unknown)?
> 
> We can return a null pointer for peoples' capabilities (if the feature isn't
> ready, and perhaps also when they're really not known), which in practice seems
> very likely to crash UIs; when we support distinguishing between "caps not
> known" and "no caps at all", perhaps we could have a global singleton for "caps
> not known" and return a const pointer to that?
> 
> Every UI is required to implement the failover from contact capabilities to
> connection capabilities by itself, rather than us doing that automatically
> (with a way for advanced UIs to detect when it's happened) as I suggested at
> the design stage. Are you *absolutely* sure that your way is better? Why?
> 
> If so, it should be explicitly documented when to do that (isUnknown() returns
> true) and what you should probably do in response (get the connection
> capabilities).
> 
The basic idea here is as follows:

If ContactCapabilities interface is supported by the connection, ContactManager::supportedFeatures will return a set that contains Contact::FeatureCapabilities. If this set does not contain FeatureCapabilities, the capabilities per user will not be known in any case, so the UI should use Connection::capabilities instead.

The UI point of view:
1) Connection is ready
2) Connection::contactManager()::supportedFeatures set contains Contact::FeatureCapabilities
3) Call ContactManager::upgradeContacts to enable Contact::FeatureCapabilities
4) Now all contacts which the capabilities were already advertised will return a proper ContactCapabilities object on Contact::capabilities() method.
5) For contact that do not have yet known capabilities (not advertised) or for those whose capabilities changed, the signal Contact::capabilitiesChanged() will be fired.

========
1) Connection is ready:
2) Connection::contactManager()::supportedFeatures set does not contain Contact::FeatureCapabilities
3) Fallback to Connection::capabilities if applicable

I don't see the need for a isUnknown/isEmpty in this scenario, and more than that, having a ContactCapabilities object for contacts that are know to have no capabilities support is having extra objects in memory for no gain. The user should fallback to ContactManager::supportedFeatures for any feature that the user may have and check the return of Contact::capabilities to know if capabilities is already known, and connect to capabilitiesChanged for change notification.

> ----
> 
> Less serious:
> 
> As already noted on IRC, we should have a configure check for
> <http://qt.gitorious.org/qt/qt/merge_requests/1657>, and class_ is ugly (I
> prefer cls).
> 
> +bool CapabilitiesBase::supportsUpgradingCalls() const
> +{
> +    QString channelType;
> +    foreach (const RequestableChannelClass &class_, mPriv->classes) {
> +        channelType = qdbus_cast<QString>(class_.fixedProperties.value(
> +                QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")));
> +        if (channelType == TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA &&
> +            !class_.allowedProperties.contains(
> +                QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
> ".ImmutableStreams"))) {
> +                // TODO should we test all classes that have channelType
> +                //      StreamedMedia or just one is fine?
> 
> I think in practice, if one channel class has immutable streams, they all will,
> so this is OK. Alter the comment to say so.
> 
> "Added CapabilitiesBase to build system." shouldn't be a separate patch; don't
> change it now, but in future, patches that add new source files should add them
> to the build at the same time.
> 
> Do we actually have ensureVideoCall etc. yet? We probably should, if the docs
> are going to reference them :-)
> 

I will work on that
Comment 5 Simon McVittie 2009-10-06 09:32:44 UTC
From IRC, with some parallel conversations deleted:

17:20 < smcv> andrunko: what you want to be able to do in a UI is: should I 
              display a microphone icon in Empathy or not?
17:21 < smcv> andrunko: if the capabilities object is always non-NULL you can 
              just do contact->capabilities()->supportsAudioCalls()
17:21 < smcv> andrunko: but if the capabilities object can be NULL, you need to 
              put a pile of NULLness checks in to not crash
17:22 < smcv> andrunko: I confidently predict that basically all UIs will want 
              to treat "unknown caps" and "known to have no caps" as synonymous
17:22 < smcv> andrunko: and similarly, they'll want to treat XMPP 
              this-guy-can-do-audio as synonymous with SIP 
              people-can-probably-do-audio
17:23 < wjt> smcv: so why should Gabble expose the difference between the two?
17:23 < wjt> (i agree with you that no UI cares about the difference)
17:23 < smcv> wjt: a valid point. perhaps it shouldn't
17:23 < smcv> wjt: although in Gabble things are clouded by the fact that there 
              are "assumed capabilities" like Text
...
17:26 < smcv> andrunko: I think I'd find it easier to see what you intend about 
              caps if you could come up with a code snippet that e.g. Kopete 
              could use for "given this not-necessarily-ready Contact, should I 
              display a microphone icon?"
17:27 < smcv> andrunko: (i.e. it would run this code snippet on setup, when 
              upgradeCapabilities returned, and on every capabilitiesChanged 
              signal)
17:28 < andrunko> but we would not have an extra object for each contact with 
                  no caps
17:28 < smcv> andrunko: I meant they'd all have a pointer to a shared singleton 
              sort of thing
17:28 < smcv> andrunko: could you post a code snippet to the bug so I can 
              understand how you intend UI code to look?
17:29 < andrunko> ahh don't know, let me post a code snippet
17:30 < smcv> andrunko: you may assume that Core is ready on the Connection if 
              you want, but I think code like this should be non-ugly if 
              optional features might still be in-progress
Comment 6 Andre Moreira Magalhaes 2009-10-06 09:42:32 UTC
Example code snippet

void UIContactManager::getContactsCaps()
{
    ContactManager *cm = m_connection->contactManager();
    if (cm->supportedFeatures().contains(Contact::FeatureCapabilities)) {
        PendingContacts *pc = cm->upgradeContacts(cm->allKnownContacts(), Contact::FeatureCapabilities);
        connect(pc,
                SIGNAL(finished(...)),
                SLOT(onContactUpgraded(...)));
    } else {
        for (uiContact, uiContacts) {
            uiContact->useConnectionCaps();
        }
    }
}

void UIContactManager::onContactUpgraded(PendingOperation *op)
{
    PendingContacts *pc = cast<>(op);
    QList<ContactPtr> contacts = op->contacts();

    for (contact, contacts) {
        uiTpContact = findUITpContactForContact(contact);
        // wathever
        uiTpContact->checkCapabilities();
    }
}

void UITpContact::UITpContact(ContactPtr contact)
    : internalContact(contact)
{
    // we can always connect here, even if Capabilities is not supported
    connect(contact,
            SIGNAL(capabilitiesChanged(...)),
            SLOT(checkCapabilities());
}

void UITpContact::checkCapabilities()
{
    if (!internalContact->capabilities()) {
        return;
    }

    // check caps and update UI
}

void UITpContact::useConnectionCaps()
{
    // connection FeatureCaps is already enabled
    // connection caps has not change notification, so just do it once

    // check caps and update UI
}
Comment 7 Andre Moreira Magalhaes 2009-10-06 11:27:47 UTC
(In reply to comment #4)
> (In reply to comment #3)
> > My most serious objections are:
> > 
> > We don't seem to provide any way to distinguish between "capabilities not yet
> > known" and "known to have no capabilities at all" (in the D-Bus API that's
> > omission from the mapping vs. an empty list). That probably requires use of
> > contains() when doing the setup? Perhaps we could have isEmpty() (true for both
> > unknown and no-caps) and isUnknown() (only true for unknown)?
> > 
> > We can return a null pointer for peoples' capabilities (if the feature isn't
> > ready, and perhaps also when they're really not known), which in practice seems
> > very likely to crash UIs; when we support distinguishing between "caps not
> > known" and "no caps at all", perhaps we could have a global singleton for "caps
> > not known" and return a const pointer to that?
> > 
> > Every UI is required to implement the failover from contact capabilities to
> > connection capabilities by itself, rather than us doing that automatically
> > (with a way for advanced UIs to detect when it's happened) as I suggested at
> > the design stage. Are you *absolutely* sure that your way is better? Why?
> > 
> > If so, it should be explicitly documented when to do that (isUnknown() returns
> > true) and what you should probably do in response (get the connection
> > capabilities).
> > 
> The basic idea here is as follows:
> 
> If ContactCapabilities interface is supported by the connection,
> ContactManager::supportedFeatures will return a set that contains
> Contact::FeatureCapabilities. If this set does not contain FeatureCapabilities,
> the capabilities per user will not be known in any case, so the UI should use
> Connection::capabilities instead.
> 
> The UI point of view:
> 1) Connection is ready
> 2) Connection::contactManager()::supportedFeatures set contains
> Contact::FeatureCapabilities
> 3) Call ContactManager::upgradeContacts to enable Contact::FeatureCapabilities
> 4) Now all contacts which the capabilities were already advertised will return
> a proper ContactCapabilities object on Contact::capabilities() method.
> 5) For contact that do not have yet known capabilities (not advertised) or for
> those whose capabilities changed, the signal Contact::capabilitiesChanged()
> will be fired.
> 
> ========
> 1) Connection is ready:
> 2) Connection::contactManager()::supportedFeatures set does not contain
> Contact::FeatureCapabilities
> 3) Fallback to Connection::capabilities if applicable
> 
> I don't see the need for a isUnknown/isEmpty in this scenario, and more than
> that, having a ContactCapabilities object for contacts that are know to have no
> capabilities support is having extra objects in memory for no gain. The user
> should fallback to ContactManager::supportedFeatures for any feature that the
> user may have and check the return of Contact::capabilities to know if
> capabilities is already known, and connect to capabilitiesChanged for change
> notification.
> 
> > ----
> > 
> > Less serious:
> > 
> > As already noted on IRC, we should have a configure check for
> > <http://qt.gitorious.org/qt/qt/merge_requests/1657>, and class_ is ugly (I
> > prefer cls).
> > 
> > +bool CapabilitiesBase::supportsUpgradingCalls() const
> > +{
> > +    QString channelType;
> > +    foreach (const RequestableChannelClass &class_, mPriv->classes) {
> > +        channelType = qdbus_cast<QString>(class_.fixedProperties.value(
> > +                QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")));
> > +        if (channelType == TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA &&
> > +            !class_.allowedProperties.contains(
> > +                QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
> > ".ImmutableStreams"))) {
> > +                // TODO should we test all classes that have channelType
> > +                //      StreamedMedia or just one is fine?
> > 
> > I think in practice, if one channel class has immutable streams, they all will,
> > so this is OK. Alter the comment to say so.
> > 
> > "Added CapabilitiesBase to build system." shouldn't be a separate patch; don't
> > change it now, but in future, patches that add new source files should add them
> > to the build at the same time.
> > 
> > Do we actually have ensureVideoCall etc. yet? We probably should, if the docs
> > are going to reference them :-)
> > 
> 
> I will work on that
> 

Done all changes
Comment 8 Simon McVittie 2009-10-06 12:31:45 UTC
Looks good now, I think. Merge merge merge! :-)
Comment 9 Andre Moreira Magalhaes 2009-10-06 12:39:31 UTC
Fixed upstream, will be in next release 0.2

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.