teaching machines

Final Project: Instant Karma – crakerbr

December 20, 2011 by . Filed under cs491 mobile, fall 2011, postmortems.

For my final project I set out to create an app to let you track your interactions with so-called “friends” to measure their true value. The app uses your phone’s contact list to assemble a list of friends, along with their photograph. It then uses its own database table to track karmic interactions and tally a score representing their current karma with you.

The main activity displaying the list of all contacts was the most interesting one. The class extends ListActivity, but I did not want a simple text-based list with one item per list. I instead had to create a new view for each item that contained the image, the name, and the current score. I had to create a custom list adapter class which needed the ability to create these miniature views. The code to build the views is as follows:

	// Build the view
	public View getView(int position, View convertView, ViewGroup parent) {
		KarmaDataTextView kdtv;
		if(convertView == null) {
			kdtv = new KarmaDataTextView(this.mContext, this.mItems.get(position));
		} else {
			kdtv = (KarmaDataTextView) convertView;
			kdtv.setIcon(this.mItems.get(position).getIcon());
			kdtv.setText(this.mItems.get(position).getText());
			kdtv.setText("" + this.mItems.get(position).getScore());
		}
		return kdtv;
	}

This process of creating a view consisting of image, text, and text is called for each contact in the phone’s database. The image used by the program is pulled from the phone, and is color shifted based on the current karmic score. This is a simple piece of code that merely increases the red component of each pixel for a negative score, or increases the blue component for a positive score. This code is also used in the ContactActivity class and is as follows:

	// Shift the color of the image of the contact based on score
	public Bitmap image_shift(Bitmap image, int score) {
		Bitmap ret = image.copy(image.getConfig(), true);
		int width = image.getWidth();
		int height = image.getHeight();
		int shift = 0;
		for(int i = 0; i < width; i++) {
			for(int j = 0; j < height; j++) {
				if(score  0) {
					shift = Math.min(Color.blue(image.getPixel(i, j)) + (score * 10), 255);
					ret.setPixel(i, j, Color.rgb(Color.red(image.getPixel(i, j)), Color.green(image.getPixel(i, j)), shift));
				} else {
					ret.setPixel(i, j, image.getPixel(i, j));
				}
				
			}
		}
		return ret;
	}

When a list item is clicked on, the app starts ContactActivity, sending the selected contact item as well as the person’s current score.

ContactActivity again shows the color shifted image as well as the name and score of the person selected. It also displays three buttons. The first is the “View History” button, which goes to a list of all previous karma entries. The next is the “Add Entry” button which allows you to enter a karma event. The last is a “Normalize” button which sends the contact text messages, either positive or negative, to bring their karma score back to a neutral zero. This activity also needs to the contact’s phone number in the phone’s database, which is accomplished with the following code:

		boolean hasPhone = (cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) == 1) ? true : false;

		if(hasPhone) {
			Cursor phones = getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contact.getContact_id(), null, null);
			phones.moveToNext();
			number = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
		}

The “Normalize” button has the following code to create its listener:

		// Setup the normalize button
		final Button normalize_button = (Button)findViewById(R.id.normalize);
		normalize_button.setOnClickListener(new OnClickListener()
        {
            public void onClick(View v)
            {
            	SmsManager sm = SmsManager.getDefault();
            	String message = (score > 0) ? "You're cool brah." :(score < 0) ? "You suck dawg." : "";
            	if((!number.equals("-1")) && (!message.equals(""))) {
            		for(int i = 0; i < Math.abs(score); i++) {
            			sm.sendTextMessage(number, null, message, null, null);
            		}
            		Toast.makeText(v.getContext(), "Sent \"" + message + "\" to " + number + " " + Math.abs(score) + " time(s).", Toast.LENGTH_SHORT).show();
            		KarmaItem item = new KarmaItem(contact.getContact_id(), "Normalized!", -score);
            		kdb = ((DBApplication)getApplication()).getKarmaDatabase();
            		kdb.insert(item);
            		score = 0;
            	} else {
            		if(score == 0) {
            			Toast.makeText(v.getContext(), "Contact is currently neutral.", Toast.LENGTH_SHORT).show();
            		} else {
            			Toast.makeText(v.getContext(), "Contact does not have a phone number.", Toast.LENGTH_SHORT).show();
            		}
            	}
            	Intent i = new Intent(v.getContext(), ContactActivity.class);
            	i.putExtra("the_contact", contact);
            	i.putExtra("the_score", score);
        		startActivity(i);
        		finish();
            }
        });

The app determines whether the score is positive or negative, then sends a message who’s content I have determined to have a karmic weight of 1 and -1 respectively to the contact a number of times equal to the magnitude of their score. If the contact is already neutral or does not have a phone number available the app tells you so. The app then starts a new instance of the ContactActivity and finish();es the current one to prevent old data from being displayed.

The KarmaHistoryActivity extends ListActivity and uses a setup similar to the main activity. The difference is that it does not need to display an image and also does not need any click behavior, so it is quite simple.

When the user clicks “Add Entry” from ContactActivity, it starts the EntryActivity, and it sends the contact’s name and ID. It also sends the contact and current score. This is sent because the ContactActivity then finish();es itself and EntryActivity will need the information to start a new ContactActivity once it finish();es. This is done to prevent stale data from being used. EntryActivity consists of an editable text to enter a description of an event, as well as a spinner to enter a score, and a “Commit” button to save the new entry to the database.

As with all of my projects this semester, I developed it using the emulator. However, when I finished I sent the app to a friend with an android phone and I was able to confirm that the “Normalize” button does indeed send text message correctly (giving him the unfortunate ability to flood my phone’s inbox). But, my friend has a list of 100+ contact on his phone, and my app takes a long time to start up and seems to have memory problems. The image processing that I do may be a bit much for a phone device, and it may not be realistic for me to keep it in.