diff --git a/keyserver/src/push/rescind.js b/keyserver/src/push/rescind.js
--- a/keyserver/src/push/rescind.js
+++ b/keyserver/src/push/rescind.js
@@ -73,8 +73,12 @@
           threadID,
         );
         deliveryPromises[id] = fcmPush({
-          notification,
-          deviceTokens: delivery.androidDeviceTokens,
+          targetedNotifications: delivery.androidDeviceTokens.map(
+            deviceToken => ({
+              deviceToken,
+              notification,
+            }),
+          ),
           codeVersion: null,
         });
       } else if (delivery.deviceType === 'ios') {
@@ -103,8 +107,10 @@
           threadID,
         );
         deliveryPromises[id] = fcmPush({
-          notification,
-          deviceTokens,
+          targetedNotifications: deviceTokens.map(deviceToken => ({
+            deviceToken,
+            notification,
+          })),
           codeVersion,
         });
       }
diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js
--- a/keyserver/src/push/send.js
+++ b/keyserver/src/push/send.js
@@ -41,10 +41,16 @@
 import { promiseAll } from 'lib/utils/promises.js';
 import { tID, tPlatformDetails, tShape } from 'lib/utils/validation-utils.js';
 
-import { prepareEncryptedIOSNotifications } from './crypto.js';
+import {
+  prepareEncryptedIOSNotifications,
+  prepareEncryptedAndroidNotifications,
+} from './crypto.js';
 import { getAPNsNotificationTopic } from './providers.js';
 import { rescindPushNotifs } from './rescind.js';
-import type { TargetedAPNsNotification } from './types.js';
+import type {
+  TargetedAPNsNotification,
+  TargetedAndroidNotification,
+} from './types.js';
 import {
   apnPush,
   fcmPush,
@@ -238,18 +244,20 @@
             platformDetails,
           );
           const deliveryPromise = (async () => {
-            const notification = await prepareAndroidNotification({
-              notifTexts,
-              newRawMessageInfos: shimmedNewRawMessageInfos,
-              threadID: threadInfo.id,
-              collapseKey: notifInfo.collapseKey,
-              badgeOnly,
-              unreadCount: unreadCounts[userID],
-              platformDetails,
-              dbID,
-            });
-            const deviceTokens = devices.map(({ deviceToken }) => deviceToken);
-            return await sendAndroidNotification(notification, deviceTokens, {
+            const targetedNotifications = await prepareAndroidNotification(
+              {
+                notifTexts,
+                newRawMessageInfos: shimmedNewRawMessageInfos,
+                threadID: threadInfo.id,
+                collapseKey: notifInfo.collapseKey,
+                badgeOnly,
+                unreadCount: unreadCounts[userID],
+                platformDetails,
+                dbID,
+              },
+              devices,
+            );
+            return await sendAndroidNotification(targetedNotifications, {
               ...notificationInfo,
               codeVersion,
             });
@@ -755,7 +763,8 @@
 });
 async function prepareAndroidNotification(
   inputData: AndroidNotifInputData,
-): Promise<Object> {
+  devices: $ReadOnlyArray<NotificationTargetDevice>,
+): Promise<$ReadOnlyArray<TargetedAndroidNotification>> {
   const convertedData = validateOutput(
     inputData.platformDetails,
     androidNotifInputDataValidator,
@@ -772,6 +781,13 @@
     dbID,
   } = convertedData;
 
+  const isTextNotification = newRawMessageInfos.every(
+    newRawMessageInfo => newRawMessageInfo.type === messageTypes.TEXT,
+  );
+
+  const shouldBeEncrypted =
+    isTextNotification && !collapseKey && codeVersion && codeVersion > 222;
+
   const notifID = collapseKey ? collapseKey : dbID;
   const { merged, ...rest } = notifTexts;
   const notification = {
@@ -802,22 +818,44 @@
     data: { ...notification.data, messageInfos },
   };
 
-  if (
-    Buffer.byteLength(JSON.stringify(copyWithMessageInfos)) <=
-    fcmMaxNotificationPayloadByteSize
-  ) {
-    return copyWithMessageInfos;
-  }
+  const evaluateAndSelectNotification = (notif, notifWithMessageInfos) => {
+    if (
+      Buffer.byteLength(JSON.stringify(notifWithMessageInfos)) <=
+      fcmMaxNotificationPayloadByteSize
+    ) {
+      return notifWithMessageInfos;
+    }
+    if (
+      Buffer.byteLength(JSON.stringify(notif)) >
+      fcmMaxNotificationPayloadByteSize
+    ) {
+      console.warn(
+        `Android notification ${notifID} exceeds size limit, even with messageInfos omitted`,
+      );
+    }
+    return notif;
+  };
 
-  if (
-    Buffer.byteLength(JSON.stringify(notification)) >
-    fcmMaxNotificationPayloadByteSize
-  ) {
-    console.warn(
-      `Android notification ${notifID} exceeds size limit, even with messageInfos omitted`,
-    );
+  const deviceTokens = devices.map(({ deviceToken }) => deviceToken);
+  if (shouldBeEncrypted) {
+    const cookieIDs = devices.map(({ cookieID }) => cookieID);
+    const [notifications, notificationsWithMessageInfos] = await Promise.all([
+      prepareEncryptedAndroidNotifications(cookieIDs, notification),
+      prepareEncryptedAndroidNotifications(cookieIDs, copyWithMessageInfos),
+    ]);
+    return notificationsWithMessageInfos.map((notif, idx) => ({
+      notification: evaluateAndSelectNotification(notifications[idx], notif),
+      deviceToken: deviceTokens[idx],
+    }));
   }
-  return notification;
+  const notificationToSend = evaluateAndSelectNotification(
+    notification,
+    copyWithMessageInfos,
+  );
+  return deviceTokens.map(deviceToken => ({
+    notification: notificationToSend,
+    deviceToken,
+  }));
 }
 
 type WebNotifInputData = {
@@ -975,8 +1013,7 @@
   invalidTokens?: $ReadOnlyArray<string>,
 };
 async function sendAndroidNotification(
-  notification: Object,
-  deviceTokens: $ReadOnlyArray<string>,
+  targetedNotifications: $ReadOnlyArray<TargetedAndroidNotification>,
   notificationInfo: NotificationInfo,
 ): Promise<AndroidResult> {
   const collapseKey = notificationInfo.collapseKey
@@ -984,11 +1021,13 @@
     : null; // for Flow...
   const { source, codeVersion } = notificationInfo;
   const response = await fcmPush({
-    notification,
-    deviceTokens,
+    targetedNotifications,
     collapseKey,
     codeVersion,
   });
+  const deviceTokens = targetedNotifications.map(
+    ({ deviceToken }) => deviceToken,
+  );
   const androidIDs = response.fcmIDs ? response.fcmIDs : [];
   const delivery: AndroidDelivery = {
     source,
@@ -1234,15 +1273,31 @@
           ? { badge: unreadCount.toString() }
           : { badge: unreadCount.toString(), badgeOnly: '1' };
       const notification = { data: notificationData };
-      const deviceTokens = deviceInfos.map(({ deviceToken }) => deviceToken);
-      deliveryPromises.push(
-        sendAndroidNotification(notification, deviceTokens, {
+      const deliveryPromise = (async () => {
+        const cookieIDs = deviceInfos.map(({ cookieID }) => cookieID);
+        let notificationsArray;
+        if (codeVersion > 222) {
+          notificationsArray = await prepareEncryptedAndroidNotifications(
+            cookieIDs,
+            notification,
+          );
+        } else {
+          notificationsArray = cookieIDs.map(() => notification);
+        }
+        const targetedNotifications = deviceInfos.map(
+          ({ deviceToken }, idx) => ({
+            deviceToken,
+            notification: notificationsArray[idx],
+          }),
+        );
+        return await sendAndroidNotification(targetedNotifications, {
           source,
           dbID,
           userID,
           codeVersion,
-        }),
-      );
+        });
+      })();
+      deliveryPromises.push(deliveryPromise);
     }
   }
 
diff --git a/keyserver/src/push/types.js b/keyserver/src/push/types.js
--- a/keyserver/src/push/types.js
+++ b/keyserver/src/push/types.js
@@ -14,3 +14,8 @@
     +[string]: string,
   },
 };
+
+export type TargetedAndroidNotification = {
+  +notification: AndroidNotification,
+  +deviceToken: string,
+};
diff --git a/keyserver/src/push/utils.js b/keyserver/src/push/utils.js
--- a/keyserver/src/push/utils.js
+++ b/keyserver/src/push/utils.js
@@ -23,7 +23,10 @@
   ensureWebPushInitialized,
   getWNSToken,
 } from './providers.js';
-import type { TargetedAPNsNotification } from './types.js';
+import type {
+  TargetedAPNsNotification,
+  TargetedAndroidNotification,
+} from './types.js';
 import { dbQuery, SQL } from '../database/database.js';
 
 const fcmTokenInvalidationErrors = new Set([
@@ -102,13 +105,11 @@
   +invalidTokens?: $ReadOnlyArray<string>,
 };
 async function fcmPush({
-  notification,
-  deviceTokens,
+  targetedNotifications,
   collapseKey,
   codeVersion,
 }: {
-  +notification: Object,
-  +deviceTokens: $ReadOnlyArray<string>,
+  +targetedNotifications: $ReadOnlyArray<TargetedAndroidNotification>,
   +codeVersion: ?number,
   +collapseKey?: ?string,
 }): Promise<FCMPushResult> {
@@ -131,7 +132,7 @@
   // won't explain which of the device tokens is invalid. So we're forced to
   // avoid the multicast functionality and call it once per deviceToken.
   const promises = [];
-  for (const deviceToken of deviceTokens) {
+  for (const { notification, deviceToken } of targetedNotifications) {
     promises.push(
       fcmSinglePush(fcmProvider, notification, deviceToken, options),
     );
@@ -146,7 +147,7 @@
     for (const error of pushResult.errors) {
       errors.push(error);
       if (fcmTokenInvalidationErrors.has(error.errorInfo.code)) {
-        invalidTokens.push(deviceTokens[i]);
+        invalidTokens.push(targetedNotifications[i].deviceToken);
       }
     }
     for (const id of pushResult.fcmIDs) {
diff --git a/keyserver/src/session/cookies.js b/keyserver/src/session/cookies.js
--- a/keyserver/src/session/cookies.js
+++ b/keyserver/src/session/cookies.js
@@ -828,7 +828,8 @@
 ): Promise<boolean> {
   if (
     !viewer.platformDetails ||
-    viewer.platformDetails.platform !== 'ios' ||
+    (viewer.platformDetails.platform !== 'ios' &&
+      viewer.platformDetails.platform !== 'android') ||
     !viewer.platformDetails.codeVersion ||
     viewer.platformDetails.codeVersion < 222
   ) {