Android Open Source - AndroidMessenger Android Messenger






From Project

Back to project page AndroidMessenger.

License

The source code is released under:

GNU General Public License

If you think the Android project AndroidMessenger listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 * @author - Zachary Goldberg @ 2008//from w  w w.j av  a2 s.  co m
 */
package com.penn.cis121.androidmessenger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import android.app.ListActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentReceiver;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.text.Layout;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.Menu.Item;
import android.view.ViewGroup.LayoutParams;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.penn.cis121.androidmessenger.accountprovider.AccountInfo;

public class AndroidMessenger extends ListActivity {

  /** Constants **/
  private final int ADD_OPTION = Menu.FIRST;
  
  private final int EDIT_OPTION = ADD_OPTION + 1;
  private final int EXIT_OPTION = EDIT_OPTION + 1;
  private final int EDIT_ACCOUNT = 0;
  
  /** Data Storage **/
  private List<ServiceConnection> serviceConnections;
  private List<ConnectionView> connectionViews;
  private HashMap<Integer,ConnectionView> viewsById;
  
  /** Instance Vars **/
  private AMListener aml;
  private BuddyListAdapter bla;
  private Logger logger;
  private boolean connectionWindowOpen = false;
  private List<Bundle> waiting;
  private int nextViewId = 1;
  /*
   * Android LifeCycle Functions
   */

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);  
    
    // Necessary data structures
    logger = Logger.getLogger(AndroidMessenger.class.getName());
    serviceConnections = new ArrayList<ServiceConnection>();
    connectionViews = new ArrayList<ConnectionView>();
    waiting = new LinkedList<Bundle>();
    viewsById = new HashMap<Integer, ConnectionView>();
    
    // Setup the intent filter that will listen to services
    aml = new AMListener(this);
    registerReceiver(aml, new IntentFilter("AndroidMessenger"));
    
    //Clear junk left over from the hack ConversationContinaer
    clearPrefs();
    
    try {
      /* VOODOO!
       * 
       * Instantiate each class once to force java
       * to run all of the static{} blocks
       * which register with AMProtocolMapper.
       * 
       * statc {} blocks are run when a class is loaded by the java 
       * runtime environment.  We need those blocks to be run to know 
       * certain information about that class.  So we force them to
       * be loaded by finding them all, and making a fake instance 
       * of them.  This enables you to simply write your own 
       * protocol files, write an appropriate static {} block,
       * throw it in the proper package, and it'll just work.  
       * 
       * Basically consider it some reflection magic.
       */      
      PackageManager m = getPackageManager();
      String pn = getPackageName();
      PackageInfo pi = m.getPackageInfo(pn, PackageManager.GET_SERVICES);
      for (android.content.pm.ServiceInfo s : pi.services) {
        Class<? extends Object> c = getClassLoader().loadClass(s.name);
        @SuppressWarnings("unused")
        Object o = c.newInstance();    
        
      }
    } catch (Exception e) {
      /* So many things cold possible go wrong with reflection
       * if any of them do we don't care too much so do nothing
       */
    }

    // Miscellaneous
    setDefaultKeyMode(SHORTCUT_DEFAULT_KEYS);
    
    // Setup the buddy list adapter
    bla = new BuddyListAdapter(this);
    
    //Load info from the database
    loadConnections();
        
    setListAdapter(bla);  
  }
  
  /** Called when we come back from conversationcontainer or anything else */
  @Override
  protected void onResume() {
    super.onResume();
    connectionWindowOpen = false;
    bla.clearAllMessageRecieved();
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    // Stop and unbind all the services
    for (ServiceConnection cv : serviceConnections) {
      unbindService(cv);
      stopService(new Intent(this, cv.getClass()));
    }
  }

  /* Logic Functions */
  
  /**
   * A function which interacts with our AccountProvider class to load 
   * all of the currently saved connections.  Populates instance fields.
   */
  private void loadConnections() {
    //Some voodoo to use the database to load saved connection information.
    logger.severe("Loading Connections");
    Uri curi = AccountInfo.Account.CONTENT_URI;
    Cursor cur = managedQuery(curi, null, null, null);
    List<ConnectionView> TempConnectionList = new ArrayList<ConnectionView>();
    if (cur.first()) {
      String username, password, className;
      int nameColumn = cur.getColumnIndex(AccountInfo.Account.USERNAME);
      int passwordColumn = cur.getColumnIndex(AccountInfo.Account.PASSWORD);
      int classColumn = cur.getColumnIndex(AccountInfo.Account.CLASSNAME);
      do {
        // Get the field values
        username = cur.getString(nameColumn);
        password = cur.getString(passwordColumn);
        className = cur.getString(classColumn);
        logger.severe("Found connection for " + username);
        // check for duplicates
        ConnectionView found = getConnection(username, password,className);
        if (found == null) { // don't want to re-make the object. We
                     // would lost the connected status etc.
          ConnectionView aimview;
          try {
            Class<? extends Object> c = getClassFromString(className);
            int id = nextViewId++;
            aimview = new ConnectionView(id,this, username, password, c);
            aimview.setText(AMProtocolMapper.getName(c) + ": " + username);
            aimview.setPadding(2, 2, 2, 2);
            aimview.setLayoutParams(new LinearLayout.LayoutParams(
                LayoutParams.FILL_PARENT,
                LayoutParams.FILL_PARENT));
            TempConnectionList.add(aimview);
          } catch (ClassNotFoundException e) {
            // delete it from the DB
            getContentResolver().delete(
                AccountInfo.Account.CONTENT_URI,
                AccountInfo.Account.USERNAME + " = \""
                    + username + "\" AND "
                    + AccountInfo.Account.PASSWORD
                    + " = \"" + password + "\" AND "
                    + AccountInfo.Account.CLASSNAME
                    + " = \"" + className + "\"", null);
          }
        } else {
          TempConnectionList.add(found);
        }
      } while (cur.next());
    }
    //Now reset and read all proper connections
    connectionViews.clear();
    bla.clearConnections();    
    for (ConnectionView cv : TempConnectionList) {
      connectionViews.add(cv);
      bla.addConnection(cv);
      viewsById.put(cv.getId(), cv);
    }
    bla.regenList();
  }
  /**
   * A function to get a class from its name
   * 
   * @param classname The class name to look up
   * @return the class represented by <i>classname</i> retrieved from the classLoader
   * @throws ClassNotFoundException
   */
  private Class<? extends Object> getClassFromString(String classname)
      throws ClassNotFoundException {
    return getClass().getClassLoader().loadClass(classname);
  }
  /**
   * Parse and handle a config coming from the AIM thread
   * 
   * @param config - a bunch of options we need to work with
   * @param cv - the connection the config came from
   */
  private void parseConfig(Bundle config, ConnectionView cv) {
    for (String s : config.keySet()) {
      try {
        Bundle buddy = config.getBundle(s);
        if (buddy == null)
          continue;
        BuddyView bv = new BuddyView(this, buddy.getString("name"),
            buddy.getString("group"), buddy.getString("alias"), cv);
        bla.addBuddy(bv);
      } catch (ClassCastException c) {
        // Some of these will happen, (username etc.) its OK
      }
    }
    bla.regenList();
  }
  /**
   * remove all buddys in the cv from the buddylist
   * @param cv the connection whose buddies need to be removed
   */
  private void removeBuddies(ConnectionView cv) {
    // TODO write this
    /*
     * if(cv == null) return; for(BuddyView bv :
     * buddiesOnConnection.get(cv)){ bla.removeBuddy(bv); } bla.regenList();
     */
  }
  /**
   * Generate a new  and add it to our internal list of connecitons
   * 
   * @param username
   * @param password
   * @param connectionClass
   * @return the new ServiceConnection
   */
  private ServiceConnection nextServiceConnection(int id,String username,  String password) {
    ServiceConnection m = new AMServiceConnection(id,username, password);
    serviceConnections.add(m);
    return m;
  }
  /**
   * Lookup a ConnectionView by Id
   * 
   * @param id
   * @return 
   */
  private ConnectionView getConnection(int id) {
    return viewsById.get(id);
  }
  /**
   * Lookup a ConnectionView by username+pw+className
   * 
   * @param username
   * @param password
   * @param className
   * @return the connectionview if found
   */
  private ConnectionView getConnection(String username, String password, String className) {
    for (ConnectionView cv : connectionViews) {
      if (cv.getUsername().equals(username)
          && cv.getPassword().equals(password)
          && cv.getConnectionClass().getName().toString().equals(
              className)) {
        return cv;
      }
    }
    logger.warning("Could not find connection for " + username + "," + password + "," + className);
    return null;
  }

  /*
   * EventHandlers and CallBacks
   */
  /**
   * When an item is clicked in the buddy list
   */  
  protected void onListItemClick(ListView l, View v, int position, long id) {
    if (v instanceof ConnectionView) {
      ConnectionView cv = (ConnectionView) v;
      ServiceConnection sc = nextServiceConnection(cv.getId(),cv.getUsername(), cv.getPassword());
      bindService(new Intent(this, cv.getConnectionClass()), sc,BIND_AUTO_CREATE);
      // TODO set some sort of global status if paused?
    } else if (v instanceof BuddyView) {
      Intent i = new Intent(this, ConversationContainer.class);
      Bundle bigBundle = new Bundle();
      bigBundle.putString("name", ((BuddyView) v).getName());
      bigBundle.putString("alias", ((BuddyView) v).getAlias());
      bigBundle.putString("username", ((BuddyView) v).getConnection().getUsername());
      bigBundle.putInt("connectionId", ((BuddyView) v).getConnection().getId());
      int ic = 0;
      for (Bundle b : waiting) {
        bigBundle.putBundle(ic + "", b);
        ic++;
      }
      waiting.clear();
      bigBundle.putInt("numwaiting", ic);
      i.putExtras(bigBundle);
      connectionWindowOpen = true;
      startActivity(i);
    }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    menu.add(0, ADD_OPTION, R.string.add_account);
    menu.add(0, EDIT_OPTION, R.string.edit_account);
    menu.add(0, EXIT_OPTION, R.string.exit_text);
    return true;
  }

  @Override
  public boolean onMenuItemSelected(int featureId, Item item) {
    super.onMenuItemSelected(featureId, item);
    switch (item.getId()) {
    case ADD_OPTION:
      Intent i = new Intent();
      i.setClass(this, AccountEditor.class);
      startSubActivity(i, EDIT_ACCOUNT);
      break;
    case EDIT_OPTION:
      View cv = getListView().getSelectedView();
      if (cv instanceof ConnectionView) {
        Intent ii = new Intent();
        ConnectionView cvs = (ConnectionView) cv;
        ii.putExtra("username", cvs.getUsername());
        ii.putExtra("password", cvs.getPassword());
        ii.putExtra("className", cvs.getConnectionClass().getName()
            .toString());
        ii.setClass(this, AccountEditor.class);
        startSubActivity(ii, EDIT_ACCOUNT);
      }
      break;
    case EXIT_OPTION:
      finish();
      break;

    }
    return true;
  }

  protected void onActivityResult(int requestCode, int resultCode,
      String data, Bundle extras) {
    switch (requestCode) {
    case EDIT_ACCOUNT:
      loadConnections();
    }

  }

  /* Private classes */
  
  /**
   * Buddy List Adapter -- our version of a BaseAdapter which provides
   * buddy list like functionality.  Incorporates necessary elements
   * for organizing lists as follows:
   * <CONNECTIONS>
   * <GROUP G>
   *   <BUDDIES In G>
   *   
   */
  private class BuddyListAdapter extends BaseAdapter {
    int count = 0;
    private Context c;
    private List<ConnectionView> cvs;
    private HashMap<String, GroupView> groups;
    private HashMap<String, HashMap<String, BuddyView>> buddyList; // Group
    // ->
    // list<BuddyView>
    private List<View> renderedViews;

    public BuddyListAdapter(Context c) {
      this.c = c;
      cvs = new ArrayList<ConnectionView>();
      buddyList = new HashMap<String, HashMap<String, BuddyView>>();
      groups = new HashMap<String, GroupView>();
    }

    public int getCount() {
      return count;
    }

    public Object getItem(int position) {
      return renderedViews.get(position);
    }

    public long getItemId(int position) {
      return ((View) getItem(position)).getId();
    }

    public View getView(int position, View convertView, ViewGroup parent) {
      return renderedViews.get(position);
    }

    public void addConnection(ConnectionView cv) {
      cvs.add(cv);
      regenList();
    }

    public void addBuddy(BuddyView bv) {
      String group = bv.getGroup();
      if (!buddyList.containsKey(group))
        buddyList.put(group, new HashMap<String, BuddyView>());
      buddyList.get(group).put(bv.getAlias().toLowerCase(), bv);
      if (!groups.containsKey(group))
        groups.put(group, new GroupView(c, group));
    }

    public void removeBuddy(BuddyView bv) {
      buddyList.get(bv.getGroup()).remove(bv.getAlias().toLowerCase());
    }

    public void setBuddyOnline(String alias, String group) {
      if (alias == null)
        return;
      if (!buddyList.containsKey(group)) {
        logger.severe("Unknown group:" + group);
        return;
      }
      if (!buddyList.get(group).containsKey(alias.toLowerCase())) {
        logger.severe("Unknown buddy:" + alias);
        return;
      }
      logger.info("Buddy SignOn: " + alias + " group" + group);
      buddyList.get(group).get(alias.toLowerCase()).setOnline(true);
      regenList();
    }

    public void setBuddyOffline(String alias, String group) {
      buddyList.get(group).get(alias.toLowerCase()).setOnline(false);
      regenList();
    }

    public void setBuddyMessageRecieved(String name) {
      for (String group : buddyList.keySet()) {
        if (buddyList.get(group).containsKey(name)) {
          BuddyView v = buddyList.get(group).get(name);
          v.setMessageRecieved(true);
        }
      }
      regenList();
    }

    public void clearAllMessageRecieved() {
      for (String group : buddyList.keySet()) {
        for (View v : buddyList.get(group).values()) {
          if (v instanceof BuddyView) {
            BuddyView bv = (BuddyView) v;
            bv.setMessageRecieved(false);
          }
        }
      }
      regenList();
    }

    public void clearConnections() {
      cvs.clear();
    }

    public void regenList() {
      renderedViews = new LinkedList<View>();
      for (ConnectionView cv : cvs) {
        if (!cv.getConnected())
          renderedViews.add(cv);
      }
      List<String> ls = new ArrayList<String>(groups.keySet());
      Collections.sort(ls);
      for (String group : ls) {
        HashMap<String, BuddyView> map = buddyList.get(group);
        List<String> buddies = new ArrayList<String>(map.keySet());
        if (buddies == null)
          continue;
        boolean groupshown = false;
        Collections.sort(buddies);
        for (String buddyAlias : buddies) {
          BuddyView bv = map.get(buddyAlias);
          if (bv.isOnline()) {
            if (!groupshown) {
              renderedViews.add(groups.get(group));
              groupshown = true;
            }
            renderedViews.add(bv);
          }
        }
      }
      count = renderedViews.size();
      // notifyDataSetChanged();// may need this one too, not sure yet
      // check for empty groups and remove
      notifyDataSetInvalidated();
      notifyDataSetChanged();
    }
  }

  private class AMServiceConnection implements ServiceConnection {

    private String username;
    private String password;
    private int cvId;

    public AMServiceConnection(int id,String username, String password) {
      this.cvId = id;
      this.username = username;
      this.password = password;
    }

    public void onServiceConnected(ComponentName name, IBinder service) {
      ConnectionView cv = getConnection(cvId);
      AMService am = AMService.Stub.asInterface((IBinder) service);
      cv.setService(am);
      try {
        am.Connect(username, password,cvId);
        cv.setText("Connecting to "
            + AMProtocolMapper.getName(cv.getConnectionClass())
            + "...");
      } catch (DeadObjectException e) {
        // TODO tell the AndroidMessenger that this service died and
        // should be remade or something
      }
    }

    public void onServiceDisconnected(ComponentName name) {
      ConnectionView cv = getConnection(cvId);
      cv.setService(null);
      // TODO tell the AndroidMessenger that this service died and should
      // be remade or something
    }

  }

  private class GroupView extends TextView {
    private String group;

    public GroupView(Context context, String group) {
      super(context);
      this.group = group;
      setText(group);
      setAlignment(Layout.Alignment.ALIGN_CENTER);
      setLayoutParams(new LinearLayout.LayoutParams(
          LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
      setFocusable(false);
      setPadding(2, 2, 2, 2);
    }

    public String getGroup() {
      return group;
    }
  }

  private class BuddyView extends TextView {
    private String name;
    private String group;
    private String alias;
    private ConnectionView connection;
    private boolean online;
    private boolean received = false;

    public BuddyView(Context context, String name, String group,
        String alias, ConnectionView cv) {
      super(context);
      this.name = name;
      this.group = group;
      this.alias = alias;
      this.connection = cv;
      if (alias == null)
        this.alias = name;
      if (this.alias != null && this.alias.length() != 0)
        setText(this.alias);
      else
        setText(this.name);
      setLayoutParams(new LinearLayout.LayoutParams(
          LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
      setFocusable(false);
      setPadding(2, 2, 2, 2);
      // status?
      // Image for Icon?
    }

    public void setMessageRecieved(boolean receivedMsg) {
      if (receivedMsg && !received)
        setText("*" + this.alias);
      else if (!receivedMsg && received)
        setText(this.alias);
      this.received = receivedMsg;
    }

    public ConnectionView getConnection() {
      return connection;
    }

    public String getName() {
      return name;
    }

    public String getGroup() {
      return group;
    }

    public String getAlias() {
      return alias;
    }

    public void setOnline(boolean b) {
      online = b;
    }

    public boolean isOnline() {
      return online;
    }

  }

  private class ConnectionView extends TextView {
    private boolean connected = false;;
    private String username;
    private String password;
    private int id;
    private Class<? extends Object> connectionClass;
    private AMService am;

    public ConnectionView(int id, Context context, String username,
        String password, Class<? extends Object> connectionClass) {
      super(context);
      this.id = id;
      this.username = username;
      this.password = password;
      this.connectionClass = connectionClass;
    }

    public void setConnected(boolean c) {
      connected = c;
    }
    public int getId(){
      return id;
    }
    public boolean getConnected() {
      return connected;
    }

    public String getUsername() {
      return username;
    }

    public String getPassword() {
      return password;
    }

    public Class<? extends Object> getConnectionClass() {
      return connectionClass;
    }

    public void setService(AMService am) {
      this.am = am;
    }

    public AMService getService() {
      return am;
    }
  }

  private class AMListener extends IntentReceiver {
    private AndroidMessenger c;

    public AMListener(AndroidMessenger c) {
      this.c = c;
    }

    @Override
    public void onReceiveIntent(Context context, Intent intent) {

      String purpose = (String) intent.getStringExtra("purpose");
      logger.info("Recieved intent with purpose: " + purpose);
      ConnectionView cv = null;
      int id = intent.getIntExtra("connectionId",-1);
      if(id != -1){
        cv = getConnection(id);
      }else{
        cv = getConnection( intent
            .getStringExtra("username"), intent
            .getStringExtra("password"), intent
            .getStringExtra("className"));
      }
      if (cv == null) {
        logger.severe("COULDNT FIND CV.... BAD!!!!!!!!!!!!!!!!!");
        logger.severe("Tried: " + id + ","+ intent.getStringExtra("username"));
        return;
      }
      if (purpose.equals("signOnSuccess")) {
        cv.setText("Connected!");
        cv.setConnected(true);
        bla.notifyDataSetChanged();
      } else if (purpose.equals("config")) {
        parseConfig(intent.getExtras(), cv);
      } else if (purpose.equals("resumeBuddyList")) {
        c.onResume();
        c.getWindow().makeActive();
        c.getTaskId();
        c.getWindow().closeAllPanels();
        getWindow().makeActive();

      } else if (purpose.equals("sendMessage")) {
        try {
          AMService am = cv.getService();
          if (am != null)
            am.sendMessage(intent.getStringExtra("buddyName"),
                intent.getStringExtra("message"));
          else {
            // TODO error
          }
        } catch (DeadObjectException e) {
          // TODO Close the service
        }
      } else if (purpose.equals("signOnError")) {
        cv.setText("Error: " + intent.getStringExtra("error"));
        cv.setConnected(false);
      } else if (purpose.equals("buddySignOn")) {
        bla.setBuddyOnline(intent.getStringExtra("alias"), intent
            .getStringExtra("group"));
      } else if (purpose.equals("buddySignOff")) {
        bla.setBuddyOffline(intent.getStringExtra("alias"), intent
            .getStringExtra("group"));
      } else if (purpose.equals("messageRecieved")) {
        if (!connectionWindowOpen) {
          bla.setBuddyMessageRecieved(intent.getStringExtra("name"));
          Bundle b = new Bundle();
          b.putString("name", intent.getStringExtra("name"));
          b.putString("message", intent.getStringExtra("message"));
          b.putString("username", intent.getStringExtra("username"));
          b.putString("password", intent.getStringExtra("password"));
          b
              .putString("className", intent
                  .getStringExtra("className"));
          waiting.add(b);
        }
      } else if (purpose.equals("disconnected")) {
        // TODO Notify of error somehow
        if (cv != null && cv.getConnected()) {
          removeBuddies(cv);
          cv.setConnected(false);
          cv.setText(AMProtocolMapper
              .getName(cv.getConnectionClass())
              + " : " + cv.getUsername());
        }
      }
    }
  }
  /**
   * Minor memory cleanup from a hack in ConversationContainer
   */
  private void clearPrefs() {
    Class<? extends Object> c = ConversationContainer.class;
    SharedPreferences.Editor editor = getSharedPreferences(c.toString() + "windows", 0).edit();
    editor.clear();
    editor.commit();
    editor = getSharedPreferences(c.toString() + "aliases", 0).edit();
    editor.clear();
    editor.commit();
    editor = getSharedPreferences(c.toString() + "tome", 0).edit();
    editor.clear();
    editor.commit();
    editor = getSharedPreferences(c.toString() + "meid", 0).edit();
    editor.clear();
    editor.commit();    
    editor = getSharedPreferences(c.toString() + "editing", 0).edit();
    editor.clear();
    editor.commit();
  }
}




Java Source Code List

com.levelonelabs.aim.AIMAdapter.java
com.levelonelabs.aim.AIMBuddy.java
com.levelonelabs.aim.AIMClient.java
com.levelonelabs.aim.AIMGroup.java
com.levelonelabs.aim.AIMListener.java
com.levelonelabs.aim.AIMSender.java
com.levelonelabs.aim.XMLizable.java
com.levelonelabs.aim.example.AIMReceptionist.java
com.levelonelabs.aim.example.EchoExample.java
com.penn.cis121.androidmessenger.AMProtocolMapper.java
com.penn.cis121.androidmessenger.AccountEditor.java
com.penn.cis121.androidmessenger.AndroidMessenger.java
com.penn.cis121.androidmessenger.ConversationContainer.java
com.penn.cis121.androidmessenger.accountprovider.AccountInfo.java
com.penn.cis121.androidmessenger.accountprovider.AccountProvider.java
com.penn.cis121.androidmessenger.protocols.Aim.java