diff --git src/bp-activity/bp-activity-filters.php src/bp-activity/bp-activity-filters.php
index 3367e75f2..be6c55f2a 100644
--- src/bp-activity/bp-activity-filters.php
+++ src/bp-activity/bp-activity-filters.php
@@ -841,3 +841,36 @@ function bp_activity_register_personal_data_exporter( $exporters ) {
 
 	return $exporters;
 }
+
+/**
+ * Generate a content body for specific activity types.
+ *
+ * @since 10.0.0
+ *
+ * @param string               $content  The content of the activity.
+ * @param BP_Activity_Activity $activity The activity object.
+ * @return string                        The content of the activity.
+ */
+function bp_activity_generate_content_body( $content = '', $activity = null ) {
+	if ( isset( $activity->type ) && 'new_avatar' === $activity->type ) {
+		$historical_avatar = '';
+
+		if ( isset( $activity->historical_avatar ) && $activity->historical_avatar ) {
+			$historical_avatar = $activity->historical_avatar;
+		} else {
+			$avatars = bp_avatar_get_version_src( $activity->user_id, $activity->date_recorded );
+
+			if ( $avatars && 1 === count( $avatars ) ) {
+				$avatar            = reset( $avatars );
+				$historical_avatar = trailingslashit( $avatar->parent_dir_url ) . $avatar->name;
+			}
+		}
+
+		if ( $historical_avatar ) {
+			$content = '<img src="' . esc_url( $historical_avatar ) . '" class="profile-photo aligncenter">';
+		}
+	}
+
+	return $content;
+}
+add_filter( 'bp_get_activity_content_body', 'bp_activity_generate_content_body', 50, 2 );
diff --git src/bp-activity/bp-activity-template.php src/bp-activity/bp-activity-template.php
index 16ee47d86..d39746f06 100644
--- src/bp-activity/bp-activity-template.php
+++ src/bp-activity/bp-activity-template.php
@@ -1430,11 +1430,35 @@ function bp_activity_content_body() {
 function bp_activity_has_content() {
 	global $activities_template;
 
-	if ( ! empty( $activities_template->activity->content ) ) {
-		return true;
+	$has_content = ! empty( $activities_template->activity->content );
+	if ( ! $has_content ) {
+		if ( 'new_avatar' === bp_get_activity_type() ) {
+			//var_dump( $activities_template );
+			$avatars = bp_avatar_get_version_src( bp_get_activity_user_id(), bp_get_activity_date_recorded() );
+
+			if ( $avatars && 1 === count( $avatars ) ) {
+				$avatar            = reset( $avatars );
+				$historical_avatar = trailingslashit( $avatar->parent_dir_url ) . $avatar->name;
+
+				// Add historical avatar to the current activity.
+				$activities_template->activity->historical_avatar = $historical_avatar;
+
+				// Update the corresponding entry into the activities template global.
+				$activity_id    = $activities_template->activity->id;
+				$activity_index = 0;
+				while ( (int) $activities_template->activities[ $activity_index ]->id !== (int) $activity_id ) {
+					$activity_index++;
+				}
+
+				$activities_template->activities[ $activity_index ]->historical_avatar = $historical_avatar;
+
+				// Force the content to be displayed.
+				$has_content = true;
+			}
+		}
 	}
 
-	return false;
+	return $has_content;
 }
 
 /**
diff --git src/bp-core/bp-core-attachments.php src/bp-core/bp-core-attachments.php
index e99ed0c14..a74069de5 100644
--- src/bp-core/bp-core-attachments.php
+++ src/bp-core/bp-core-attachments.php
@@ -409,24 +409,28 @@ function bp_attachments_create_item_type( $type = 'avatar', $args = array() ) {
 
 	// It's an avatar, we need to crop it.
 	if ( 'avatar' === $type ) {
-		$created = bp_core_avatar_handle_crop( array(
-			'object'        => $r['object'],
-			'avatar_dir'    => trim( dirname( $attachment_data['subdir'] ), '/' ),
-			'item_id'       => (int) $r['item_id'],
-			'original_file' => trailingslashit( $attachment_data['subdir'] ) . $image_file_name,
-			'crop_w'        => $r['crop_w'],
-			'crop_h'        => $r['crop_h'],
-			'crop_x'        => $r['crop_x'],
-			'crop_y'        => $r['crop_y']
-		) );
+		$created = bp_core_avatar_handle_crop(
+			array(
+				'object'        => $r['object'],
+				'avatar_dir'    => trim( dirname( $attachment_data['subdir'] ), '/' ),
+				'item_id'       => (int) $r['item_id'],
+				'original_file' => trailingslashit( $attachment_data['subdir'] ) . $image_file_name,
+				'crop_w'        => $r['crop_w'],
+				'crop_h'        => $r['crop_h'],
+				'crop_x'        => $r['crop_x'],
+				'crop_y'        => $r['crop_y']
+			)
+		);
 
 	// It's a cover image we need to fit it to feature's dimensions.
 	} elseif ( 'cover_image' === $type ) {
-		$cover_image = bp_attachments_cover_image_generate_file( array(
-			'file'            => $image_file_path,
-			'component'       => $r['component'],
-			'cover_image_dir' => $attachment_data['path']
-		) );
+		$cover_image = bp_attachments_cover_image_generate_file(
+			array(
+				'file'            => $image_file_path,
+				'component'       => $r['component'],
+				'cover_image_dir' => $attachment_data['path']
+			)
+		);
 
 		$created = ! empty( $cover_image['cover_file'] );
 	}
@@ -1597,3 +1601,89 @@ function bp_attachments_cover_image_ajax_delete() {
 	}
 }
 add_action( 'wp_ajax_bp_cover_image_delete', 'bp_attachments_cover_image_ajax_delete' );
+
+/**
+ * List the files of a directory.
+ *
+ * @since 10.0.0
+ *
+ * @param string $directory_path Absolute path of a directory.
+ * @return array The list of the files inside the directory.
+ */
+function bp_attachments_list_directory_files( $directory_path = '' ) {
+	if ( ! is_dir( $directory_path ) ) {
+		return array();
+	}
+
+	$files    = array();
+	$iterator = new FilesystemIterator( $directory_path, FilesystemIterator::SKIP_DOTS );
+
+	foreach ( $iterator as $file ) {
+		$_file = new stdClass();
+
+		$_file->name               = $file->getfilename();
+		$_file->path               = $file->getPathname();
+		$_file->size               = $file->getSize();
+		$_file->type               = $file->getType();
+		$_file->mime_type          = mime_content_type( $_file->path );
+		$_file->last_modified      = $file->getMTime();
+		$_file->latest_access_date = $file->getATime();
+		$_file->id                 = pathinfo( $_file->name, PATHINFO_FILENAME );
+		$files[ $_file->id ]       = $_file;
+	}
+
+	return $files;
+}
+
+/**
+ * List the files of a directory recursively and eventually find a file using its ID.
+ *
+ * @since 10.0.0
+ *
+ * @param string $directory_path Absolute path of a directory.
+ * @param string $find           The file ID to find into the directory or its children.
+ * @return array The list of the files.
+ */
+function bp_attachments_list_directory_files_recursively( $directory_path = '', $find = '' ) {
+	if ( ! is_dir( $directory_path ) ) {
+		return array();
+	}
+
+	$files     = array();
+	$directory = new RecursiveDirectoryIterator( $directory_path, FilesystemIterator::SKIP_DOTS );
+	$iterator  = new RecursiveIteratorIterator( $directory, RecursiveIteratorIterator::CHILD_FIRST );
+	$bp_upload = bp_upload_dir();
+
+	foreach ( $iterator as $file ) {
+		$_file = new stdClass();
+
+		$_file->name               = $file->getfilename();
+		$_file->path               = $file->getPathname();
+		$_file->size               = $file->getSize();
+		$_file->type               = $file->getType();
+		$_file->mime_type          = mime_content_type( $_file->path );
+		$_file->last_modified      = $file->getMTime();
+		$_file->latest_access_date = $file->getATime();
+		$_file->parent_dir_path    = dirname( $_file->path );
+		$_file->parent_dir_url     = str_replace( $bp_upload['basedir'], $bp_upload['baseurl'], $_file->parent_dir_path );
+		$_file->id                 = pathinfo( $_file->name, PATHINFO_FILENAME );
+
+		// Ensure URL is https if SSL is set/forced.
+		if ( is_ssl() ) {
+			$_file->parent_dir_url = str_replace( 'http://', 'https://', $_file->parent_dir_url );
+		}
+
+		$file_id = $_file->id;
+		if ( $_file->parent_dir_path !== $directory_path ) {
+			$file_id = trailingslashit( str_replace( trailingslashit( $directory_path ), '', $_file->parent_dir_path ) ) . $file_id;
+		}
+
+		$files[ $file_id ] = $_file;
+	}
+
+	if ( $find ) {
+		return wp_filter_object_list( $files, array( 'id' => $find ) );
+	}
+
+	return $files;
+}
diff --git src/bp-core/bp-core-avatars.php src/bp-core/bp-core-avatars.php
index 301c6bfdc..fad91b34f 100644
--- src/bp-core/bp-core-avatars.php
+++ src/bp-core/bp-core-avatars.php
@@ -1176,12 +1176,14 @@ add_action( 'wp_ajax_bp_avatar_upload', 'bp_avatar_ajax_upload' );
  * Handle avatar webcam capture.
  *
  * @since 2.3.0
+ * @since 10.0.0 Adds the `$return` param to eventually return the crop result.
  *
  * @param string $data    Base64 encoded image.
  * @param int    $item_id Item to associate.
- * @return bool True on success, false on failure.
+ * @param string $return  Whether to get the crop `array` or a `boolean`. Defaults to `boolean`.
+ * @return array|bool True on success, false on failure.
  */
-function bp_avatar_handle_capture( $data = '', $item_id = 0 ) {
+function bp_avatar_handle_capture( $data = '', $item_id = 0, $return = 'boolean' ) {
 	if ( empty( $data ) || empty( $item_id ) ) {
 		return false;
 	}
@@ -1237,6 +1239,10 @@ function bp_avatar_handle_capture( $data = '', $item_id = 0 ) {
 		// Crop to default values.
 		$crop_args = array( 'item_id' => $item_id, 'original_file' => $avatar_to_crop, 'crop_x' => 0, 'crop_y' => 0 );
 
+		if ( 'array' === $return ) {
+			return bp_core_avatar_handle_crop( $crop_args, 'array' );
+		}
+
 		return bp_core_avatar_handle_crop( $crop_args );
 	} else {
 		return false;
@@ -1247,6 +1253,7 @@ function bp_avatar_handle_capture( $data = '', $item_id = 0 ) {
  * Crop an uploaded avatar.
  *
  * @since 1.1.0
+ * @since 10.0.0 Adds the `$return` param to eventually return the crop result.
  *
  * @param array|string $args {
  *     Array of function parameters.
@@ -1265,9 +1272,10 @@ function bp_avatar_handle_capture( $data = '', $item_id = 0 ) {
  *     @type int         $crop_x        The horizontal starting point of the crop. Default: 0.
  *     @type int         $crop_y        The vertical starting point of the crop. Default: 0.
  * }
- * @return bool True on success, false on failure.
+ * @param string       $return Whether to get the crop `array` or a `boolean`. Defaults to `boolean`.
+ * @return array|bool True or the crop result on success, false on failure.
  */
-function bp_core_avatar_handle_crop( $args = '' ) {
+function bp_core_avatar_handle_crop( $args = '', $return = 'boolean' ) {
 
 	$r = bp_parse_args(
 		$args,
@@ -1306,6 +1314,10 @@ function bp_core_avatar_handle_crop( $args = '' ) {
 		return false;
 	}
 
+	if ( 'array' === $return ) {
+		return $cropped;
+	}
+
 	return true;
 }
 
@@ -1352,19 +1364,25 @@ function bp_avatar_ajax_set() {
 			$webcam_avatar = base64_decode( $webcam_avatar );
 		}
 
-		if ( ! bp_avatar_handle_capture( $webcam_avatar, $avatar_data['item_id'] ) ) {
+		$cropped_webcam_avatar = bp_avatar_handle_capture( $webcam_avatar, $avatar_data['item_id'], 'array' );
+
+		if ( ! $cropped_webcam_avatar ) {
 			wp_send_json_error( array(
 				'feedback_code' => 1
 			) );
 
 		} else {
 			$return = array(
-				'avatar' => esc_url( bp_core_fetch_avatar( array(
-					'object'  => $avatar_data['object'],
-					'item_id' => $avatar_data['item_id'],
-					'html'    => false,
-					'type'    => 'full',
-				) ) ),
+				'avatar' => esc_url(
+					bp_core_fetch_avatar(
+						array(
+							'object'  => $avatar_data['object'],
+							'item_id' => $avatar_data['item_id'],
+							'html'    => false,
+							'type'    => 'full',
+						)
+					)
+				),
 				'feedback_code' => 2,
 				'item_id'       => $avatar_data['item_id'],
 			);
@@ -1376,12 +1394,14 @@ function bp_avatar_ajax_set() {
 			 * Fires if the new avatar was successfully captured.
 			 *
 			 * @since 6.0.0
+			 * @since 10.0.0 Adds a new param: an array containing the full, thumb avatar and the timestamp.
 			 *
-			 * @param string $item_id     Inform about the user id the avatar was set for.
-			 * @param string $type        Inform about the way the avatar was set ('camera').
-			 * @param array  $avatar_data Array of parameters passed to the avatar handler.
+			 * @param string $item_id               Inform about the user id the avatar was set for.
+			 * @param string $type                  Inform about the way the avatar was set ('camera').
+			 * @param array  $avatar_data           Array of parameters passed to the crop handler.
+			 * @param array  $cropped_webcam_avatar Array containing the full, thumb avatar and the timestamp.
 			 */
-			do_action( 'bp_members_avatar_uploaded', (int) $avatar_data['item_id'], $avatar_data['type'], $avatar_data );
+			do_action( 'bp_members_avatar_uploaded', (int) $avatar_data['item_id'], $avatar_data['type'], $avatar_data, $cropped_webcam_avatar );
 
 			wp_send_json_success( $return );
 		}
@@ -1413,14 +1433,20 @@ function bp_avatar_ajax_set() {
 	);
 
 	// Handle crop.
-	if ( bp_core_avatar_handle_crop( $r ) ) {
+	$cropped_avatar = bp_core_avatar_handle_crop( $r, 'array' );
+
+	if ( $cropped_avatar ) {
 		$return = array(
-			'avatar' => esc_url( bp_core_fetch_avatar( array(
-				'object'  => $avatar_data['object'],
-				'item_id' => $avatar_data['item_id'],
-				'html'    => false,
-				'type'    => 'full',
-			) ) ),
+			'avatar' => esc_url(
+				bp_core_fetch_avatar(
+					array(
+						'object'  => $avatar_data['object'],
+						'item_id' => $avatar_data['item_id'],
+						'html'    => false,
+						'type'    => 'full',
+					)
+				)
+			),
 			'feedback_code' => 2,
 			'item_id'       => $avatar_data['item_id'],
 		);
@@ -1430,10 +1456,10 @@ function bp_avatar_ajax_set() {
 			do_action_deprecated( 'xprofile_avatar_uploaded', array( (int) $avatar_data['item_id'], $avatar_data['type'], $r ), '6.0.0', 'bp_members_avatar_uploaded' );
 
 			/** This action is documented in bp-core/bp-core-avatars.php */
-			do_action( 'bp_members_avatar_uploaded', (int) $avatar_data['item_id'], $avatar_data['type'], $r );
+			do_action( 'bp_members_avatar_uploaded', (int) $avatar_data['item_id'], $avatar_data['type'], $r, $cropped_avatar );
 		} elseif ( 'group' === $avatar_data['object'] ) {
 			/** This action is documented in bp-groups/bp-groups-screens.php */
-			do_action( 'groups_avatar_uploaded', (int) $avatar_data['item_id'], $avatar_data['type'], $r );
+			do_action( 'groups_avatar_uploaded', (int) $avatar_data['item_id'], $avatar_data['type'], $r, $cropped_avatar );
 		}
 
 		wp_send_json_success( $return );
@@ -2122,3 +2148,36 @@ function bp_avatar_template_check() {
 		bp_attachments_get_template_part( 'avatars/index' );
 	}
 }
+
+/**
+ * Get a specific version of an avatar from its history
+ *
+ * @since 10.0.0
+ *
+ * @param int        $user_id   The user ID we need the avatar version for.
+ * @param int|string $timestamp An integer Unix timestamp or a date string of the format 'Y-m-d h:i:s'.
+ * @param string     $type      The type of avatar we need. Possible values are `thumb` and `full`.
+ * @return array                A list of matching results, an empty array if no avatars were found.
+ */
+function bp_avatar_get_version_src( $user_id = 0, $timestamp = '', $type = 'full' ) {
+	if ( ! $user_id || ! $timestamp ) {
+		return array();
+	}
+
+	// The avatar ID is the avatar file name without dot extension.
+	$avatar_id = '';
+
+	if ( ! is_numeric( $timestamp ) ) {
+		$timestamp = strtotime( $timestamp );
+	}
+
+	$avatar_id = $timestamp . '-bpfull';
+	if ( 'full' !== $type ) {
+		$avatar_id = $timestamp . '-bpthumb';
+	}
+
+	// The user avatar directory we are looking into to get the avatar url.
+	$user_avatar_dir = trailingslashit( bp_core_avatar_upload_path() ) . 'avatars/' . $user_id;
+
+	return bp_attachments_list_directory_files_recursively( $user_avatar_dir, $avatar_id );
+}
diff --git src/bp-core/classes/class-bp-attachment-avatar.php src/bp-core/classes/class-bp-attachment-avatar.php
index d95ad4c4c..f13188bdd 100644
--- src/bp-core/classes/class-bp-attachment-avatar.php
+++ src/bp-core/classes/class-bp-attachment-avatar.php
@@ -199,7 +199,7 @@ class BP_Attachment_Avatar extends BP_Attachment {
 	 * @see  BP_Attachment::crop for the list of parameters
 	 *
 	 * @param array $args Array of arguments for the cropping.
-	 * @return array The cropped avatars (full and thumb).
+	 * @return array The cropped avatars (full, thumb and the timestamp).
 	 */
 	public function crop( $args = array() ) {
 		// Bail if the original file is missing.
@@ -255,10 +255,36 @@ class BP_Attachment_Avatar extends BP_Attachment {
 
 		/**
 		 * Check that the new avatar doesn't have the same name as the
-		 * old one before deleting
+		 * old one before moving the previous one into history.
 		 */
 		if ( ! empty( $existing_avatar ) && $existing_avatar !== $this->url . $relative_path ) {
-			bp_core_delete_existing_avatar( array( 'object' => $args['object'], 'item_id' => $args['item_id'], 'avatar_path' => $avatar_folder_dir ) );
+			// Add a new revision for the existing avatar.
+			$avatars = bp_attachments_list_directory_files( $avatar_folder_dir );
+
+			if ( $avatars ) {
+				foreach ( $avatars as $avatar_file ) {
+					if ( ! isset( $avatar_file->name, $avatar_file->id, $avatar_file->path ) ) {
+						continue;
+					}
+
+					$is_full  = preg_match( "/-bpfull/", $avatar_file->name );
+					$is_thumb = preg_match( "/-bpthumb/", $avatar_file->name );
+
+					if ( $is_full || $is_thumb ) {
+						$revision = $this->add_revision(
+							'avatar',
+							array(
+								'file_abspath' => $avatar_file->path,
+								'file_id'      => $avatar_file->id,
+							)
+						);
+
+						if ( is_wp_error( $revision ) ) {
+							error_log( $revision->get_error_message() );
+						}
+					}
+				}
+			}
 		}
 
 		// Make sure we at least have minimal data for cropping.
@@ -272,11 +298,16 @@ class BP_Attachment_Avatar extends BP_Attachment {
 
 		// Get the file extension.
 		$data = @getimagesize( $absolute_path );
-		$ext  = $data['mime'] == 'image/png' ? 'png' : 'jpg';
+		$ext  = $data['mime'] === 'image/png' ? 'png' : 'jpg';
 
 		$args['original_file'] = $absolute_path;
 		$args['src_abs']       = false;
-		$avatar_types = array( 'full' => '', 'thumb' => '' );
+
+		$avatar_types = array(
+			'full'  => '',
+			'thumb' => '',
+		);
+		$timestamp   = bp_core_current_time( false, 'timestamp' );
 
 		foreach ( $avatar_types as $key_type => $type ) {
 			if ( 'thumb' === $key_type ) {
@@ -287,7 +318,7 @@ class BP_Attachment_Avatar extends BP_Attachment {
 				$args['dst_h'] = bp_core_avatar_full_height();
 			}
 
-			$filename         = wp_unique_filename( $avatar_folder_dir, uniqid() . "-bp{$key_type}.{$ext}" );
+			$filename         = wp_unique_filename( $avatar_folder_dir, $timestamp . "-bp{$key_type}.{$ext}" );
 			$args['dst_file'] = $avatar_folder_dir . '/' . $filename;
 
 			$avatar_types[ $key_type ] = parent::crop( $args );
@@ -296,8 +327,13 @@ class BP_Attachment_Avatar extends BP_Attachment {
 		// Remove the original.
 		@unlink( $absolute_path );
 
-		// Return the full and thumb cropped avatars.
-		return $avatar_types;
+		// Return the full, thumb cropped avatars and the timestamp.
+		return array_merge(
+			$avatar_types,
+			array(
+				'timestamp' => $timestamp,
+			)
+		);
 	}
 
 	/**
diff --git src/bp-core/classes/class-bp-attachment.php src/bp-core/classes/class-bp-attachment.php
index 9362a6c12..248c44946 100644
--- src/bp-core/classes/class-bp-attachment.php
+++ src/bp-core/classes/class-bp-attachment.php
@@ -559,6 +559,75 @@ abstract class BP_Attachment {
 		return $script_data;
 	}
 
+	/**
+	 * Adds a new revision of a file.
+	 *
+	 * @since 10.0.0
+	 *
+	 * @param string $attachment_type The attachement type (eg: avatar).
+	 * @param array $args {
+	 *     @type string $file_abspath The source file (absolute path) for the attachment.
+	 *     @type string $file_id      Optional. The file ID to use as a suffix for the revision directory.
+	 * }
+	 * @return object|WP_Error An object informing about the URL an Path to a revision file, a WP_Error object on failure.
+	 */
+	public function add_revision( $attachment_type, $args = array() ) {
+		$r = bp_parse_args(
+			$args,
+			array(
+				'file_abspath' => '',
+				'file_id'      => '',
+			),
+			'attachment_' . $attachment_type . '_add_revision'
+		);
+
+		if ( ! $r['file_abspath'] ) {
+			return new WP_Error( 'missing_parameter', __( 'The absolute path to your file is missing.', 'buddypress' ) );
+
+			// Make sure it's coming from an uploaded file.
+		} elseif ( false === strpos( $r['file_abspath'], $this->upload_path ) ) {
+			return new WP_Error( 'forbidden_path', __( 'The absolute path to your file is not allowed.', 'buddypress' ) );
+
+		} else {
+			$filepath = $r['file_abspath'];
+		}
+
+		$dirname  = trailingslashit( dirname( $filepath ) );
+		$filename = sanitize_file_name( wp_basename( $filepath ) );
+
+		if ( ! $r['file_id'] ) {
+			$r['file_id'] = $filename;
+		}
+
+		$file_id = wp_hash( $r['file_id'] );
+
+		// Set the revision name & dir.
+		$revision_name = '';
+		$revision_dir  = $dirname . '._revisions_' . $file_id;
+
+		// Avatars and Cover Images are specific attachments.
+		if ( 'avatar' === $attachment_type || 'cover_image' === $attachment_type ) {
+			$revision_dir  = $dirname . 'history';
+		}
+
+		// Create the revision directory if it doesn't exist yet.
+		if ( ! is_dir( $revision_dir ) ) {
+			mkdir( $revision_dir );
+		}
+
+		$revision_name = wp_unique_filename( $revision_dir, $filename );
+		$revision_path = trailingslashit( $revision_dir ) . $revision_name;
+
+		if ( ! rename( $filepath, $revision_path ) ) {
+			return new WP_Error( 'missing_parameter', __( 'An unexpected error occured while adding the revision.', 'buddypress' ) );
+		}
+
+		return (object) array(
+			'url'  => str_replace( trailingslashit( $this->upload_path ), trailingslashit( $this->url ), $revision_path ),
+			'path' => $revision_path,
+		);
+	}
+
 	/**
 	 * Get full data for an image
 	 *
diff --git src/bp-groups/actions/create.php src/bp-groups/actions/create.php
index 132429bd1..54e2cc589 100644
--- src/bp-groups/actions/create.php
+++ src/bp-groups/actions/create.php
@@ -270,19 +270,23 @@ function groups_action_create_group() {
 				'crop_h'        => $_POST['h']
 			);
 
-			if ( ! bp_core_avatar_handle_crop( $args ) ) {
+			$cropped_avatar = bp_core_avatar_handle_crop( $args, 'array' );
+
+			if ( ! $cropped_avatar ) {
 				bp_core_add_message( __( 'There was an error saving the group profile photo, please try uploading again.', 'buddypress' ), 'error' );
 			} else {
 				/**
 				 * Fires after a group avatar is uploaded.
 				 *
 				 * @since 2.8.0
+				 * @since 10.0.0 Adds a new param: an array containing the full, thumb avatar and the timestamp.
 				 *
-				 * @param int    $group_id ID of the group.
-				 * @param string $type     Avatar type. 'crop' or 'full'.
-				 * @param array  $args     Array of parameters passed to the avatar handler.
+				 * @param int    $group_id       ID of the group.
+				 * @param string $type           Avatar type. 'crop' or 'camera'.
+				 * @param array  $args           Array of parameters passed to the crop handler.
+				 * @param array  $cropped_avatar Array containing the full, thumb avatar and the timestamp.
 				 */
-				do_action( 'groups_avatar_uploaded', bp_get_current_group_id(), 'crop', $args );
+				do_action( 'groups_avatar_uploaded', bp_get_current_group_id(), 'crop', $args, $cropped_avatar );
 
 				bp_core_add_message( __( 'The group profile photo was uploaded successfully.', 'buddypress' ) );
 			}
diff --git src/bp-groups/screens/single/admin/group-avatar.php src/bp-groups/screens/single/admin/group-avatar.php
index 3e2927a1e..57fc59451 100644
--- src/bp-groups/screens/single/admin/group-avatar.php
+++ src/bp-groups/screens/single/admin/group-avatar.php
@@ -74,19 +74,23 @@ function groups_screen_group_admin_avatar() {
 			'crop_h'        => $_POST['h']
 		);
 
-		if ( !bp_core_avatar_handle_crop( $args ) ) {
+		$cropped_avatar = bp_core_avatar_handle_crop( $args, 'array' );
+
+		if ( ! $cropped_avatar ) {
 			bp_core_add_message( __( 'There was a problem cropping the group profile photo.', 'buddypress' ), 'error' );
 		} else {
 			/**
 			 * Fires after a group avatar is uploaded.
 			 *
 			 * @since 2.8.0
+			 * @since 10.0.0 Adds a new param: an array containing the full, thumb avatar and the timestamp.
 			 *
-			 * @param int    $group_id ID of the group.
-			 * @param string $type     Avatar type. 'crop' or 'full'.
-			 * @param array  $args     Array of parameters passed to the avatar handler.
+			 * @param int    $group_id       ID of the group.
+			 * @param string $type           Avatar type. 'crop' or 'camera'.
+			 * @param array  $args           Array of parameters passed to the avatar handler.
+			 * @param array  $cropped_avatar Array containing the full, thumb avatar and the timestamp.
 			 */
-			do_action( 'groups_avatar_uploaded', bp_get_current_group_id(), 'crop', $args );
+			do_action( 'groups_avatar_uploaded', bp_get_current_group_id(), 'crop', $args, $cropped_avatar );
 			bp_core_add_message( __( 'The new group profile photo was uploaded successfully.', 'buddypress' ) );
 		}
 	}
@@ -109,4 +113,4 @@ function groups_screen_group_admin_avatar() {
 	 */
 	bp_core_load_template( apply_filters( 'groups_template_group_admin_avatar', 'groups/single/home' ) );
 }
-add_action( 'bp_screens', 'groups_screen_group_admin_avatar' );
\ No newline at end of file
+add_action( 'bp_screens', 'groups_screen_group_admin_avatar' );
diff --git src/bp-members/bp-members-activity.php src/bp-members/bp-members-activity.php
index 890ff2317..1645c1542 100644
--- src/bp-members/bp-members-activity.php
+++ src/bp-members/bp-members-activity.php
@@ -166,10 +166,14 @@ add_action( 'bp_core_activated_user', 'bp_core_new_user_activity' );
  * Adds an activity stream item when a user has uploaded a new avatar.
  *
  * @since 8.0.0
+ * @since 10.0.0 Adds the `$type`, `$crop_data` and `$cropped_avatar` parameters.
  *
- * @param int $user_id The user id the avatar was set for.
+ * @param int    $user_id        The user id the avatar was set for.
+ * @param string $type           The way the avatar was set ('camera' or `crop`).
+ * @param array  $crop_data      Array of parameters passed to the crop handler.
+ * @param array  $cropped_avatar Array containing the full, thumb avatar and the timestamp.
  */
-function bp_members_new_avatar_activity( $user_id = 0 ) {
+function bp_members_new_avatar_activity( $user_id = 0, $type = '', $crop_data = array(), $cropped_avatar = array() ) {
 
 	// Bail if activity component is not active.
 	if ( ! bp_is_active( 'activity' ) ) {
@@ -230,13 +234,19 @@ function bp_members_new_avatar_activity( $user_id = 0 ) {
 		}
 	}
 
+	$recorded_time = '';
+	if ( isset( $cropped_avatar['timestamp'] ) && $cropped_avatar['timestamp'] ) {
+		$recorded_time = date( 'Y-m-d H:i:s', $cropped_avatar['timestamp'] );
+	}
+
 	// Add the activity.
-	bp_activity_add(
+	$activity_id = bp_activity_add(
 		array(
-			'user_id'   => $user_id,
-			'component' => $bp->members->id,
-			'type'      => 'new_avatar',
+			'user_id'       => $user_id,
+			'component'     => $bp->members->id,
+			'type'          => 'new_avatar',
+			'recorded_time' => $recorded_time,
 		)
 	);
 }
-add_action( 'bp_members_avatar_uploaded', 'bp_members_new_avatar_activity' );
+add_action( 'bp_members_avatar_uploaded', 'bp_members_new_avatar_activity', 10, 4 );
diff --git src/bp-members/screens/change-avatar.php src/bp-members/screens/change-avatar.php
index 5c4bcf440..6a421ec77 100644
--- src/bp-members/screens/change-avatar.php
+++ src/bp-members/screens/change-avatar.php
@@ -61,7 +61,10 @@ function bp_members_screen_change_avatar() {
 			'crop_h'        => $_POST['h']
 		);
 
-		if ( ! bp_core_avatar_handle_crop( $args ) ) {
+		// Handle crop.
+		$cropped_avatar = bp_core_avatar_handle_crop( $r, 'array' );
+
+		if ( ! $cropped_avatar ) {
 			bp_core_add_message( __( 'There was a problem cropping your profile photo.', 'buddypress' ), 'error' );
 		} else {
 
@@ -72,11 +75,14 @@ function bp_members_screen_change_avatar() {
 			 * Fires right before the redirect, after processing a new avatar.
 			 *
 			 * @since 6.0.0
+			 * @since 10.0.0 Adds a new param: an array containing the full, thumb avatar and the timestamp.
 			 *
-			 * @param string $item_id Inform about the user id the avatar was set for.
-			 * @param string $value   Inform about the way the avatar was set ('crop').
+			 * @param string $item_id        Inform about the user id the avatar was set for.
+			 * @param string $type           Inform about the way the avatar was set ('camera').
+			 * @param array  $args           Array of parameters passed to the crop handler.
+			 * @param array  $cropped_avatar Array containing the full, thumb avatar and the timestamp.
 			 */
-			do_action( 'bp_members_avatar_uploaded', (int) $args['item_id'], 'crop' );
+			do_action( 'bp_members_avatar_uploaded', (int) $args['item_id'], 'crop', $args, $cropped_avatar );
 
 			bp_core_add_message( __( 'Your new profile photo was uploaded successfully.', 'buddypress' ) );
 			bp_core_redirect( bp_displayed_user_domain() );
diff --git tests/phpunit/testcases/core/attachments.php tests/phpunit/testcases/core/attachments.php
new file mode 100644
index 000000000..4acbf496a
--- /dev/null
+++ tests/phpunit/testcases/core/attachments.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @group core
+ */
+
+class BP_Tests_Core_Attachments extends BP_UnitTestCase {
+	/**
+	 * @group bp_attachments_list_directory_files_recursively
+	 */
+	public function test_bp_attachments_list_directory_files_recursively() {
+		$files = bp_attachments_list_directory_files_recursively( BP_TESTS_DIR . 'assets', 'index' );
+
+		$this->assertTrue( 1 === count( $files ) );
+		$this->assertTrue( isset( $files['templates/index'] ) );
+	}
+}
diff --git tests/phpunit/testcases/core/class-bp-attachment.php tests/phpunit/testcases/core/class-bp-attachment.php
index fae4e150d..6bb1c308b 100644
--- tests/phpunit/testcases/core/class-bp-attachment.php
+++ tests/phpunit/testcases/core/class-bp-attachment.php
@@ -406,6 +406,84 @@ class BP_Tests_BP_Attachment_TestCases extends BP_UnitTestCase {
 		$this->clean_files( 'shrink' );
 	}
 
+	/**
+	 * @group add_revision
+	 */
+	public function test_bp_attachment_add_revision() {
+		if ( false === _wp_image_editor_choose() || version_compare( phpversion(), '7.0' , '<' ) ) {
+			$this->markTestSkipped( 'This test requires PHP >= 7.0 and to have a valid image editor that is compatible with WordPress.' );
+		}
+
+		$image = BP_TESTS_DIR . 'assets/upside-down.jpg';
+
+		$attachment = new BPTest_Attachment_Extension(
+			array(
+				'base_dir'   => 'add_revision',
+				'action'     => 'attachment_action',
+				'file_input' => 'attachment_file_input',
+			)
+		);
+
+		$abs_path_copy = $attachment->upload_path . '/upside-down.jpg';
+		copy( $image, $abs_path_copy );
+
+		$revision = $attachment->add_revision(
+			'media',
+			array(
+				'file_abspath' => $abs_path_copy,
+				'file_id'      => 'media',
+			)
+		);
+
+		$this->assertFalse( file_exists( $abs_path_copy ) );
+		$this->assertTrue( file_exists( $revision->path ) );
+
+		// Cleanup
+		@unlink( $revision->path );
+		@rmdir( dirname( $revision->path ) );
+		$this->clean_files( 'add_revision' );
+	}
+
+	/**
+	 * @group add_revision
+	 * @group avatars
+	 */
+	public function test_bp_attachment_add_avatar_history() {
+		if ( false === _wp_image_editor_choose() || version_compare( phpversion(), '7.0' , '<' ) ) {
+			$this->markTestSkipped( 'This test requires PHP >= 7.0 and to have a valid image editor that is compatible with WordPress.' );
+		}
+
+		$image = BP_TESTS_DIR . 'assets/upside-down.jpg';
+
+		$attachment = new BPTest_Attachment_Extension(
+			array(
+				'base_dir'   => 'add_history',
+				'action'     => 'attachment_action',
+				'file_input' => 'attachment_file_input',
+			)
+		);
+
+		$abs_path_copy = $attachment->upload_path . '/upside-down.jpg';
+		copy( $image, $abs_path_copy );
+
+		$revision = $attachment->add_revision(
+			'avatar',
+			array(
+				'file_abspath' => $abs_path_copy,
+				'file_id'      => 'avatar',
+			)
+		);
+
+		$this->assertFalse( file_exists( $abs_path_copy ) );
+		$this->assertTrue( file_exists( $revision->path ) );
+		$this->assertSame( $attachment->url . '/history/upside-down.jpg', $revision->url );
+
+		// Cleanup
+		@unlink( $revision->path );
+		@rmdir( dirname( $revision->path ) );
+		$this->clean_files( 'add_history' );
+	}
+
 	public function limit_to_50px( $max_width ) {
 		return 50;
 	}
