// Update the subscriptions we maintain to agree with the current contact state
void AppearanceGroup::updateSubscriptions()
{
   Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                 "AppearanceGroup::updateSubscriptions mUri = '%s'",
                 mSharedUser.data());

   // First, scan mSubscriptions to construct a list of all the
   // call-id/contact combinations.
   // (If a phone reboots and starts registering with a different Call-Id,
   // the call-id/contact combination will be different even if the contact
   // URI is unchanged.  So the new registration will appear to be different
   // to this machinery, and it will establish a new Appearance to
   // the contact URI.  This compensates for the fact that the previous
   // Appearance to the contact URI appears to the Appearance Agent to be
   // working but the phone no longer knows of the subscription.  The
   // reg events will eventually terminate the old combination and we
   // will delete its Appearance.)
   UtlHashBag callid_contacts;

   UtlHashMapIterator subs_itor(mSubscriptions);
   while (subs_itor())
   {
      if (Os::Logger::instance().willLog(FAC_SAA, PRI_DEBUG))
      {
         Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                       "AppearanceGroup::updateSubscriptions subscription '%s'",
                       (dynamic_cast <UtlString*> (subs_itor.key()))->data());
      }
      UtlHashMap* contact_state =
         dynamic_cast <UtlHashMap*> (subs_itor.value());
      Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                    "AppearanceGroup::updateSubscriptions contact_state = %p",
                    contact_state);
      UtlHashMapIterator contact_itor(*contact_state);
      while (contact_itor())
      {
         UtlString* contact =
            dynamic_cast <UtlString*> (contact_itor.value());
         if (Os::Logger::instance().willLog(FAC_SAA, PRI_DEBUG))
         {
            Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                          "AppearanceGroup::updateSubscriptions contact id '%s', Call-Id/URI '%s'",
                          (dynamic_cast <UtlString*> (contact_itor.key()))->data(),
                          contact->data());
         }
         // Check if the contact is already in callid_contacts.
         if (!callid_contacts.find(contact))
         {
            // If not, add it.
            UtlString* c = new UtlString(*contact);
            callid_contacts.insert(c);
            Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                          "AppearanceGroup::updateSubscriptions contact '%s' added", c->data());
         }
      }
   }

   // Now that we have a clean list of callid_contacts, update
   // Appearances to match it.

   // If we both terminate subscriptions and create subscriptions,
   // wait a short while to allow the terminations to complete.  This
   // should not be necessary, but it makes life easier on Polycom
   // phones which (at this time) cannot support two subscriptions at
   // a time, and if the termination of the old subscription arrives
   // after the initiation of the new subscription, the new
   // subscription will be lost.
   // This variable tracks whether such a wait is needed before a
   // subscription is started.
   bool subscription_ended_but_no_wait_done_yet = false;

   // Iterate through the list of Appearances and remove any that aren't
   // in callid_contacts.
   {
      UtlHashMapIterator itor(mAppearances);
      UtlString* ss;
      while ((ss = dynamic_cast <UtlString*> (itor())))
      {
         if (!callid_contacts.find(ss))
         {
            Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                          "AppearanceGroup::updateSubscriptions deleting subscription for '%s' in mUri = '%s'",
                          ss->data(), mSharedUser.data());
            // Terminate all dialogs for this Appearance, then publish - before we delete it
            bool bContentChanged = false;
            SipDialogEvent* lPartialContent = new SipDialogEvent("partial", mSharedUser.data());
            Appearance* inst = dynamic_cast <Appearance*> (itor.value());
            bContentChanged = inst->terminateDialogs(true); // terminate all dialogs
            inst->getDialogs(lPartialContent);
            if (bContentChanged)
            {
               lPartialContent->buildBody();
               publish(true, true, lPartialContent);
               delete lPartialContent;
            }
            mAppearances.destroy(ss);
            subscription_ended_but_no_wait_done_yet = true;
         }
         else
         {
            Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                          "AppearanceGroup::updateSubscriptions found subscription for '%s' in mUri = '%s'",
                          ss->data(), mSharedUser.data());
         }
      }
   }

   // Iterate through callid_contacts and add an Appearance for
   // any that aren't in mAppearances.
   // We don't limit the number of additions here, as the size of
   // callid_contacts is guarded by the tests in notifyEventCallback.
   {
      UtlHashBagIterator itor(callid_contacts);
      UtlString* callid_contact;
      while ((callid_contact = dynamic_cast <UtlString*> (itor())))
      {
         if (!mAppearances.find(callid_contact))
         {
            // If we both terminate subscriptions and create subscriptions,
            // wait a short while to allow the terminations to complete.
            if (SUBSCRIPTION_WAIT > 0)
            {
               if (subscription_ended_but_no_wait_done_yet)
               {
                  Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                                "AppearanceGroup::updateSubscriptions waiting for %d msec",
                                SUBSCRIPTION_WAIT);
                  OsTask::delay(SUBSCRIPTION_WAIT);
                  subscription_ended_but_no_wait_done_yet = false;
               }
            }

            Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                          "AppearanceGroup::updateSubscriptions adding subscription for '%s' in mUri = '%s'",
                          callid_contact->data(), mSharedUser.data());
            // Get the contact URI into a UtlString.
            UtlString uri(callid_contact->data() +
                          callid_contact->index(';') +
                          1);
            mAppearances.insertKeyAndValue(new UtlString(*callid_contact),
                                           new Appearance(getAppearanceAgent(), this,  uri)
                                           );
         }
         else
         {
            Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                          "AppearanceGroup::updateSubscriptions using existing subscription for '%s' in mUri = '%s'",
                          callid_contact->data(), mSharedUser.data());
         }
      }
   }

   // Free callid_contacts.
   callid_contacts.destroyAll();
}
// Destructor
AppearanceGroup::~AppearanceGroup()
{
   Os::Logger::instance().log(FAC_SAA, PRI_DEBUG,
                 "AppearanceGroup::~ this = %p, mSharedUser = '******'",
                 this, mSharedUser.data());
   // Delete this AppearanceGroup from mSubscribeMap (for the "reg" subscription).
   getAppearanceAgent()->getAppearanceGroupSet().deleteSubscribeMapping(&mSubscriptionEarlyDialogHandle);

   // End the "reg" subscription.
   UtlBoolean ret;
   ret = getAppearanceAgent()->getSubscribeClient().
      endSubscriptionGroup(mSubscriptionEarlyDialogHandle);
   Os::Logger::instance().log(FAC_SAA,
                 ret ? PRI_INFO : PRI_WARNING,
                 "AppearanceGroup::~ endSubscriptionGroup %s mSharedUser = '******', mSubscriptionEarlyDialogHandle = '%s'",
                 ret ? "succeeded" : "failed",
                 mSharedUser.data(),
                 mSubscriptionEarlyDialogHandle.data());

   // Remove this AppearanceGroup from mNotifyMap for all subscriptions.
   {
      UtlHashMapIterator itor(mSubscriptions);
      UtlString* handle;
      while ((handle = dynamic_cast <UtlString*> (itor())))
      {
         getAppearanceAgent()->getAppearanceGroupSet().deleteNotifyMapping(handle);
      }
   }

   // Delete the contents of mSubscriptions.
   mSubscriptions.destroyAll();

   // Terminate all dialogs for this shared Appearance Group, then publish
   bool bContentChanged = false;
   SipDialogEvent* lPartialContent = new SipDialogEvent("partial", mSharedUser.data());
   {
      UtlHashMapIterator itor(mAppearances);
      UtlString* handle;
      while ( (handle = dynamic_cast <UtlString*> (itor())) )
      {
         Appearance* pApp = dynamic_cast <Appearance*> (itor.value());
         bContentChanged |= pApp->terminateDialogs(true); // terminate all dialogs
         pApp->getDialogs(lPartialContent);
      }
   }
   if (bContentChanged)
   {
      lPartialContent->buildBody();
      publish(true, true, lPartialContent);
   }
   delete lPartialContent;

   // Delete the subordinate Appearance's for the contacts.
   {
      // Have to use a loop to remove items individually, because
      // destroyAll() deadlocks with the access to mAppearances
      // during the the attempt to publish new status for the resource
      // as the Appearances are recursively deleted.
      int changeDelay = getAppearanceAgent()->getChangeDelay();
      UtlHashMapIterator itor(mAppearances);
      UtlContainable* k;
      while ((k = itor()))
      {
         UtlContainable* v = itor.value();
         mAppearances.removeReference(k);
         delete k;
         delete v;
         // Delay to allow the consequent processing to catch up.
         OsTask::delay(changeDelay);
      }
   }
}