diff --git bp-xprofile/bp-xprofile-activity.php bp-xprofile/bp-xprofile-activity.php
index 01ffcc3..98f2e0f 100644
--- bp-xprofile/bp-xprofile-activity.php
+++ bp-xprofile/bp-xprofile-activity.php
@@ -132,3 +132,111 @@ function bp_xprofile_new_avatar_activity() {
 	) );
 }
 add_action( 'xprofile_avatar_uploaded', 'bp_xprofile_new_avatar_activity' );
+
+/**
+ * Add an activity item when a user has updated his profile.
+ *
+ * @since BuddyPress (2.0.0)
+ *
+ * @param int $user_id ID of the user who has updated his profile.
+ * @param array $field_ids IDs of the fields submitted.
+ * @param bool $errors True if validation or saving errors occurred, otherwise
+ *        false.
+ * @param array $old_values Pre-save xprofile field values and visibility
+ *        levels.
+ * @param array $new_values Post-save xprofile field values and visibility
+ *        levels.
+ * @return bool True on success, false on failure.
+ */
+function bp_xprofile_updated_profile_activity( $user_id, $field_ids, $errors, $old_values = array(), $new_values = array() ) {
+	// If there were errors, don't post
+	if ( ! empty( $errors ) ) {
+		return false;
+	}
+
+	// Don't post if there have been no changes, or if the changes are
+	// related solely to non-public fields
+	$public_changes = false;
+	foreach ( $new_values as $field_id => $new_value ) {
+		$old_value       = isset( $old_values[ $field_id ] ) ? $old_values[ $field_id ] : '';
+		$old_value_value = isset( $old_value['value'] ) ? $old_value['value'] : '';
+		$old_value_visibility = isset( $old_value['visibility'] ) ? $old_value['visibility'] : '';
+
+		// Don't register changes to private fields
+		if ( 'public' !== $new_value['visibility'] ) {
+			continue;
+		}
+
+		// Don't register if there have been no changes
+		if ( $new_value === $old_value ) {
+			continue;
+		}
+
+		// Looks like we have public changes - no need to keep checking
+		$public_changes = true;
+		break;
+	}
+
+	if ( ! $public_changes ) {
+		return false;
+	}
+
+	// Throttle to one activity of this type per 2 hours
+	$existing = bp_activity_get( array(
+		'max' => 1,
+		'filter' => array(
+			'user_id' => $user_id,
+			'object'  => buddypress()->profile->id,
+			'action'  => 'updated_profile',
+		),
+	) );
+
+	if ( empty( $existing['activities'] ) ) {
+		$throttle = false;
+	} else {
+		// Default throttle time is 2 hours. Filter to change (in seconds)
+		$throttle_period = apply_filters( 'bp_xprofile_updated_profile_activity_throttle_time', 60 * 60 * 2 );
+		$then = strtotime( $existing['activities'][0]->date_recorded );
+		$now  = strtotime( bp_core_current_time() );
+
+		$throttle = ( $now - $then ) < $throttle_period;
+	}
+
+	if ( $throttle ) {
+		return false;
+	}
+
+	// If we've reached this point, assemble and post the activity item
+
+	// Note for translators: The natural phrasing in English, "Joe updated
+	// his profile", requires that we know Joe's gender, which we don't. If
+	// your language doesn't have this restriction, feel free to use a more
+	// natural translation.
+	$profile_link = trailingslashit( bp_core_get_user_domain( $user_id ) . buddypress()->profile->slug );
+	$action = sprintf( __( '%1$s&#8217;s profile was updated', 'buddypress' ), '<a href="' . $profile_link . '">' . bp_core_get_user_displayname( $user_id ) . '</a>' );
+
+	$retval = xprofile_record_activity( array(
+		'user_id'      => $user_id,
+		'action'       => $action,
+		'primary_link' => $profile_link,
+		'component'    => buddypress()->profile->id,
+		'type'         => 'updated_profile',
+	) );
+
+	return (bool) $retval;
+}
+add_action( 'xprofile_updated_profile', 'bp_xprofile_updated_profile_activity', 10, 5 );
+
+/**
+ * Add filters for xprofile activity types to Show dropdowns.
+ *
+ * @since BuddyPress (2.0.0)
+ */
+function xprofile_activity_filter_options() {
+	?>
+
+	<option value="updated_profile"><?php _e( 'Profile Updates', 'buddypress' ) ?></option>
+
+	<?php
+}
+add_action( 'bp_activity_filter_options', 'xprofile_activity_filter_options' );
diff --git bp-xprofile/bp-xprofile-screens.php bp-xprofile/bp-xprofile-screens.php
index 2f9a9e6..db39dba 100644
--- bp-xprofile/bp-xprofile-screens.php
+++ bp-xprofile/bp-xprofile-screens.php
@@ -102,6 +102,7 @@ function xprofile_screen_edit_profile() {
 			$errors = false;
 
 			// Now we've checked for required fields, lets save the values.
+			$old_values = $new_values = array();
 			foreach ( (array) $posted_field_ids as $field_id ) {
 
 				// Certain types of fields (checkboxes, multiselects) may come through empty. Save them as an empty array so that they don't get overwritten by the default on the next edit.
@@ -120,9 +121,18 @@ function xprofile_screen_edit_profile() {
 				// Save the visibility level
 				$visibility_level = !empty( $_POST['field_' . $field_id . '_visibility'] ) ? $_POST['field_' . $field_id . '_visibility'] : 'public';
 				xprofile_set_field_visibility_level( $field_id, bp_displayed_user_id(), $visibility_level );
+
+				$old_values[ $field_id ] = array(
+					'value'      => xprofile_get_field_data( $field_id, bp_displayed_user_id() ),
+					'visibility' => xprofile_get_field_visibility_level( $field_id, bp_displayed_user_id() ),
+				);
+				$new_values[ $field_id ] = array(
+					'value'      => $value,
+					'visibility' => $visibility_level,
+				);
 			}
 
-			do_action( 'xprofile_updated_profile', bp_displayed_user_id(), $posted_field_ids, $errors );
+			do_action( 'xprofile_updated_profile', bp_displayed_user_id(), $posted_field_ids, $errors, $old_values, $new_values );
 
 			// Set the feedback messages
 			if ( !empty( $errors ) ) {
diff --git tests/testcases/xprofile/activity.php tests/testcases/xprofile/activity.php
new file mode 100644
index 0000000..358575c
--- /dev/null
+++ tests/testcases/xprofile/activity.php
@@ -0,0 +1,283 @@
+<?php
+
+/**
+ * @group xprofile
+ * @group activity
+ */
+class BP_Tests_XProfile_Activity extends BP_UnitTestCase {
+	protected $updated_profile_data = array();
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_with_errors() {
+		$d = $this->setup_updated_profile_data();
+
+		// Fake new/old values to ensure a change
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo2',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), true ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_throttled() {
+		$d = $this->setup_updated_profile_data();
+
+		$time = time();
+		$prev_time = date( 'Y-m-d h:i:s', $time - ( 119 * 60 ) );
+		$now_time = date( 'Y-m-d h:i:s', $time );
+
+		$this->factory->activity->create( array(
+			'user_id' => $d['u'],
+			'component' => buddypress()->profile->id,
+			'type' => 'updated_profile',
+			'date_recorded' => $prev_time,
+		) );
+
+		// Fake new/old values to ensure a change
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo2',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_outside_of_throttle() {
+		$d = $this->setup_updated_profile_data();
+
+		$time = time();
+		$prev_time = date( 'Y-m-d h:i:s', $time - ( 121 * 60 ) );
+		$now_time = date( 'Y-m-d h:i:s', $time );
+
+		$this->factory->activity->create( array(
+			'user_id' => $d['u'],
+			'component' => buddypress()->profile->id,
+			'type' => 'updated_profile',
+			'recorded_time' => $prev_time,
+		) );
+
+		// Fake new/old values to ensure a change
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo2',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false, $old_values, $new_values ) );
+
+		$existing = bp_activity_get( array(
+			'max' => 1,
+			'filter' => array(
+				'user_id' => $user_id,
+				'object' => buddypress()->profile->id,
+				'action' => 'updated_profile',
+			),
+		) );
+
+		$this->assertEquals( 1, $existing['total'] );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_no_existing_activity() {
+		$d = $this->setup_updated_profile_data();
+
+		// Fake new/old values to ensure a change
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo2',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false, $old_values, $new_values ) );
+
+		$existing = bp_activity_get( array(
+			'max' => 1,
+			'filter' => array(
+				'user_id' => $user_id,
+				'object' => buddypress()->profile->id,
+				'action' => 'updated_profile',
+			),
+		) );
+
+		$this->assertEquals( 1, $existing['total'] );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_no_changes() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_no_public_changes() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'loggedin',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'bar',
+				'visibility' => 'loggedin',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_public_changed_to_private() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'bar',
+				'visibility' => 'loggedin',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_private_changed_to_public() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'loggedin',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_field_didnt_previously_exist() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array();
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'bar',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_public_changes() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f']->id => array(
+				'value'      => 'bar',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f']->id ), false, $old_values, $new_values ) );
+	}
+
+
+	protected function setup_updated_profile_data() {
+		$this->updated_profile_data['u'] = $this->create_user();
+		$this->updated_profile_data['g'] = $this->factory->xprofile_group->create();
+		$this->updated_profile_data['f'] = $this->factory->xprofile_field->create( array(
+			'type' => 'textbox',
+			'field_group_id' => $this->updated_profile_data['g']->id,
+		) );
+
+	}
+}
