diff --git a/native/keyboard/keyboard-state-container.react.js b/native/keyboard/keyboard-state-container.react.js
--- a/native/keyboard/keyboard-state-container.react.js
+++ b/native/keyboard/keyboard-state-container.react.js
@@ -1,5 +1,6 @@
 // @flow
 
+import * as MediaLibrary from 'expo-media-library';
 import * as React from 'react';
 import { Platform } from 'react-native';
 import { KeyboardUtils } from 'react-native-keyboard-input';
@@ -68,6 +69,15 @@
     }
     if (this.state.mediaGalleryOpen && !prevState.mediaGalleryOpen) {
       void (async () => {
+        if (Platform.OS === 'android') {
+          // The promise returned from MediaLibrary.requestPermissionsAsync
+          // never resolves on Android if it's invoked from
+          // MediaGalleryKeyboard, which runs in a special environment via
+          // react-native-keyboard-input. To get around this, we invoke it
+          // here, and then use MediaLibrary.getPermissionsAsync in
+          // MediaGalleryKeyboard
+          void MediaLibrary.requestPermissionsAsync();
+        }
         await sleep(tabBarAnimationDuration);
         await waitForInteractions();
         this.setState({ renderKeyboardInputHost: true });
diff --git a/native/media/media-gallery-keyboard.react.js b/native/media/media-gallery-keyboard.react.js
--- a/native/media/media-gallery-keyboard.react.js
+++ b/native/media/media-gallery-keyboard.react.js
@@ -419,7 +419,14 @@
   };
 
   async getPermissions(): Promise<boolean> {
-    const { granted } = await MediaLibrary.requestPermissionsAsync();
+    let granted = false;
+    if (Platform.OS === 'android') {
+      const result = await MediaLibrary.getPermissionsAsync();
+      granted = result.granted;
+    } else {
+      const result = await MediaLibrary.requestPermissionsAsync();
+      granted = result.granted;
+    }
     if (!granted) {
       this.guardedSetState({ error: "don't have permission :(" });
     }