ContactInfoManager.java :  » Client » android-gadu » pl » szpadel » android » gadu » Android Open Source

Android Open Source » Client » android gadu 
android gadu » pl » szpadel » android » gadu » ContactInfoManager.java
package pl.szpadel.android.gadu;

import java.io.StringReader;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;

import pl.szpadel.android.gadu.packets.AddNotify;
import pl.szpadel.android.gadu.packets.BasePacket;
import pl.szpadel.android.gadu.packets.Notify;
import pl.szpadel.android.gadu.packets.NotifyReply80;
import pl.szpadel.android.gadu.packets.ReceivedPacket;
import pl.szpadel.android.gadu.packets.UserlistReply80;
import pl.szpadel.android.gadu.packets.UserlistRequest;
import pl.szpadel.android.gadu.packets.ZeroLenghtSendPacket;
import android.content.Context;
import android.database.DataSetObserver;

/** Singleton containing all contact data */
public class ContactInfoManager extends Object implements IAppComponent {

  private static final String TAG = "ContactInfoManager";
  
  ///////////////////////
  // Contacts db
  
  /// Actual database
  private HashMap<Long, ContactInfo> mById = new HashMap<Long, ContactInfo>();
  
  /// UIn of logged in user for which contacts are showed
  private long mCurrentUin = 0;
  
  /// buffer from incoming userlist messages
  private ArrayList<ByteBuffer> mReceivedBuffers = new ArrayList<ByteBuffer>();
  
  /// Contacts to which we subscribed in the server
  private HashSet<Long> mSubscribedContacts = new HashSet<Long>();
  
  /// Flag - contact were loaded from DB during this session
  private boolean mContactsLoaded = false;
  
  ContactDatabaseOpenHelper mDBHelper;

  /// Adds user to the info manager, or updates if already exists
  public synchronized void addContact(ContactInfo contact) {
    if (mById.containsKey(contact.id)) {
      updateInDB(contact);
    } else {
      addToDB(contact);
    }
    mById.put(contact.id, contact);
    
    // if connected and not subscribed to contact state, do it.
    if (App.getInstance().getConnectionState().getState() == ConnectionState.CONNECTED) {
      if (! mSubscribedContacts.contains(contact.id)) {
        subscribeToSingleContact(contact.id);
      }
    }
    
    notifyObservers();
  }
  
  /// Returns contact by uin
  public synchronized ContactInfo getByUin(long uin) {
    return mById.get(uin);
  }
  
  /// Request download from server
  public void importContactListFromServer() {
    AGLog.i(TAG, "importContactListFromServer");
    // create req message
    App.getInstance().getCommunicationService().sendPacket(new UserlistRequest());
    // prepare to receive packets
    mReceivedBuffers.clear();
  }
  
  /// Handles incoming userlist packet
  private void handleUserlistPacket(UserlistReply80 packet) {
    AGLog.i(TAG, "onUserlistPacket");
    
    mReceivedBuffers.add(packet.data);
    if (packet.last) {
      // count total data
      int compressedDataLen = 0;
      for (ByteBuffer b: mReceivedBuffers) {
        compressedDataLen += b.limit();
      }
      AGLog.i(TAG, "total compressed data size:" + compressedDataLen);
      // allocate buffer to hold them all
      ByteBuffer compressedData = ByteBuffer.allocate(compressedDataLen);
      for (ByteBuffer b: mReceivedBuffers) {
        b.rewind();
        compressedData.put(b);
      }
      // allocate data for decompressed
      byte[] decompressedData = new byte[compressedDataLen*100]; // i don't like this
      // decompress
      Inflater inflater = new Inflater();
      inflater.setInput(compressedData.array());
      
      try {
        int decompressedDataLen = inflater.inflate(decompressedData, 0, decompressedData.length);
        AGLog.i(TAG, "decompressed data size:"+ decompressedDataLen);
        String asString = new String(decompressedData, 0, decompressedDataLen);
        //AGLog.i(TAG, "Contact list:\n" + asString);
        parseServerContactList(asString);
      } catch (DataFormatException e) {
        AGLog.e(TAG, "Error decompressing received list:"+ e.getMessage());
      }
      
    }
  }
  
  /// Updates 
  
  /// Returns shallow copy all contacts
  public synchronized Collection<ContactInfo> getContacts() {
    return mById.values();
  }
  
  /// Private constructor
  public ContactInfoManager(Context ctx) {
    mDBHelper = new ContactDatabaseOpenHelper(ctx);
  }

  /////////////////////////////////////
  // Change notifications

  /// Set of observes. We need no more than one instance of every observer
  private HashSet<DataSetObserver> mObservers = new HashSet<DataSetObserver>();
  
  /// Flag - bulk change in progress
  boolean mBulkChange = false;
  
  /// changes during bulk change operation
  int mBulkChangeChanges = 0;
  
  ////////////////////////////////////////////////////////
  // interface
  
  /// starts bulk change - all changes will be applied only after endChanges() is called
  public void beginChanges() {
    assert(mBulkChange == false);
    
    mBulkChange = true;
    mBulkChangeChanges = 0;
  }
  
  /// Ends bulk change. Notifies observes if something happened since last beginChanges()
  public void endChanges() {
    assert(mBulkChange == true);
    mBulkChange = false;
    
    if (mBulkChangeChanges > 0 ) {
      for (DataSetObserver observer : mObservers ) {
        observer.onChanged();
      }
      mBulkChangeChanges = 0;
    }
  }
  
  public void registerDataSetObserver(DataSetObserver observer) {
    mObservers.add(observer);
  }

  public void unregisterDataSetObserver(DataSetObserver observer) {
    mObservers.remove(observer);
    
  }

  // Notifies observers of change
  private void notifyObservers() {
    if (mBulkChange == true) {
      AGLog.d(TAG, "notifyObservers: doing nothing, bulk change");
      mBulkChangeChanges++;
    } else {
      AGLog.d(TAG, "notifyObservers: notyfying");
      for (DataSetObserver observer : mObservers ) {
        observer.onChanged();
      }
    }
  }
  
  @Override
  public void onPacket(ReceivedPacket packet) {
    
      switch(packet.getType()) {
      case BasePacket.TYPE_NOTIFY_REPLY80:
      case BasePacket.TYPE_STATUS80:
        updateStatuses((NotifyReply80) packet);
        break;
      case BasePacket.TYPE_USERLIST_REPLY80:
        handleUserlistPacket((UserlistReply80)packet);
        break;
      default:
        ; // do nothing
      }
    
  }

  @Override
  public void onConnectionStateChanged(ConnectionState state) {
     // just connected?
    if (state.getState() == ConnectionState.CONNECTED) {
      // logged in user changed?
      if (mCurrentUin != App.getInstance().getConfig().getUin()) {
        // clear current user list
        mById.clear();
        mContactsLoaded = false;
        mCurrentUin = App.getInstance().getConfig().getUin();
        notifyObservers();
      }
      // load contacts from DB if not yet loaded
      if (!mContactsLoaded) {
        loadContactsFromDB();
        mContactsLoaded = true;
      }
      // subscribe to contacts
      mSubscribedContacts.clear();
      subscribeToAllContacts();
    }
    // disconnected?
    else {
      // set all statuses to 'offline'
      AGLog.i(TAG, "Connection lost, setting al statuses to offline");
      setAllContactsOffline();
    }
    
  }
  
  // Helper - translates uin to name
  public String uinToName(long uin) {
    if (mById.containsKey(uin)){
      ContactInfo ci = mById.get(uin);
      if (ci.displayName != "") {
        return ci.displayName;
      }
    }
    
    return Long.toString(uin);
  }
  
  /// Loads contact from DB
  public void loadContacts() {
    if (!mContactsLoaded) {
      loadContactsFromDB();
      mContactsLoaded = true;
    }
  }

  /// deletes contact
  public void deleteContact(long uin) {
    if (mById.containsKey(uin)){
      mById.remove(uin);
      mDBHelper.deleteContact(uin);
      notifyObservers();
    }
  }

  /////////////////////////////////////////////////////////
  // Implementation
  
  private void subscribeToAllContacts() {
    // subscribes to all contacts
    // send contact list
    AGLog.i(TAG, "Subscrining to the entire contact list");
    if (mById.isEmpty()){
      AGLog.d(TAG, "Sending empty contact list");
      ZeroLenghtSendPacket cl = new ZeroLenghtSendPacket(BasePacket.TYPE_LIST_EMPTY);
      App.getInstance().getCommunicationService().sendPacket(cl);
    }
    else {
      // TODO handle contact lists above 400
      assert(mById.size() <= 400);
      AGLog.d(TAG, "Sending list notification");
      Notify notify = new Notify(mById.values(), true);
      App.getInstance().getCommunicationService().sendPacket(notify);
      
      mSubscribedContacts.addAll(mById.keySet());
    }    
  }
  
  private void subscribeToSingleContact(long id) {
    AGLog.i(TAG, "Subscribint to single packet: " + id);
    AddNotify packet = new AddNotify((int)id);
    App.getInstance().getCommunicationService().sendPacket(packet);
  }

  
  /// contact list parser handler
  private class ContactListHandler extends DefaultHandler {
    
    /// Last element content
    private String mElementContent = new String();
    ContactInfo mContact = null; // currently parsed contact
    
    /// On element started
    public void startElement (String uri, String localName, String qName, Attributes attributes) {
      if (localName == "Contact") {
        mContact = new ContactInfo("", -1);
      }
    }
    /// On element content
    public void characters (char[] ch, int start, int length) {
      mElementContent = new String(ch, start, length);
    }
    /// On element ended
    public void endElement (String uri, String localName, String qName) {
      if (mContact != null) {
        try {
          AGLog.d(TAG, "parsing contact param: "+ localName + " = " + mElementContent);
          
          if (localName == "GGNumber") mContact.id = Long.parseLong(mElementContent);
          else if (localName == "ShowName" )mContact.displayName = mElementContent;
          else if (localName == "MobilePhone" )mContact.mobilePhone = mElementContent;
          else if (localName == "HomePhone" )mContact.homePhone = mElementContent;
          else if (localName == "Email" )mContact.email = mElementContent;
          else if (localName == "FirstName" )mContact.firstName = mElementContent;
          else if (localName == "LastName" )mContact.lastName = mElementContent;
          else if (localName == "WwwAddress" )mContact.wwwAdrress = mElementContent;
          else if (localName == "Gender" )mContact.gender = mElementContent;
          else if (localName == "Birth" )mContact.birth = mElementContent;
          else if (localName == "City" )mContact.city = mElementContent;
          else if (localName == "Contact") {
            // validate
            if (mContact.id < 0){
              AGLog.e(TAG, "got from XML conatct with no GGNumber: " + mContact);
            } else {
              addContact(mContact);
            }
            mContact = null;
          }
        } catch (Exception e) {
          AGLog.e(TAG, "Error parsing contact: " + mContact);
          mContact = null; // will skip it and wait for the next one
        }
      }
    }
  }
  
  /// Parses contact list as send by a server
  // format description: http://dev.gg.pl/api/pages/formaty_plikow.html
  private void parseServerContactList(String xml) {
    AGLog.i(TAG, "parsing xml");
    
    try {
      SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
      InputSource is = new InputSource(new StringReader(xml));
      
      beginChanges();
      parser.parse(is, new ContactListHandler());
      endChanges();
      
      // set user list to top
      if (!mById.isEmpty()) {
        ContactListActivity activity = App.getInstance().getContactListActivity();
        if (activity != null) {
          activity.setSelection(0);
        }
      }
      
    } catch (Exception e) {
      AGLog.e(TAG, "Error parsing contact list:" + e.getMessage());
    }
    
  }
  
    /// Updates statuses
    private void updateStatuses(NotifyReply80 statuses) {
      for (NotifyReply80.StatusInfo status : statuses.statuses){
        AGLog.d(TAG, "Updating status: " + status);
        
        ContactInfo ci = mById.get(status.uin);
        if (ci != null) {
          AGLog.i(TAG, "Updating status for user " + status.uin + " to " + status.status);
          ci.status = status.status;
        }
      }
      notifyObservers();
    }
    
    /// Sets all contact statuses to offline
  private void setAllContactsOffline() {
    // change all statuses
    for(ContactInfo ci : mById.values()) {
      ci.status = new Status(Status.GG_STATUS_NOT_AVAIL, ci.status.getDescription());
    }
    // tell everyone
    notifyObservers();
  }

  @Override
  public void onStatusChanged(Status status) {
    // nothing
    
  }

  @Override
  public void onConfigChanged() {
    // TODO Auto-generated method stub
    
  }
  
  /// Adds single contacts to DB
  private void addToDB(ContactInfo contact) {
    mDBHelper.insert(contact);
  }
  
  /// updates single contact in db
  private void updateInDB(ContactInfo contact) {
    mDBHelper.update(contact);
  }
  
  /// load all contacts from DB
  private void loadContactsFromDB() {
    Collection<ContactInfo> contacts = mDBHelper.getAllContacts();
    if (!contacts.isEmpty()) {
      for (ContactInfo info : contacts) {
        mById.put(info.id, info);
      }
      notifyObservers();
    }
  }
  

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.