diff --git a/native/push/push-handler.react.js b/native/push/push-handler.react.js --- a/native/push/push-handler.react.js +++ b/native/push/push-handler.react.js @@ -31,6 +31,7 @@ recordNotifPermissionAlertActionType, shouldSkipPushPermissionAlert, } from 'lib/utils/push-alerts.js'; +import sleep from 'lib/utils/sleep.js'; import { androidNotificationChannelID, @@ -113,6 +114,7 @@ currentState: ?string = getCurrentLifecycleState(); appStarted = 0; androidNotificationsEventSubscriptions: Array = []; + androidNotificationsPermissionPromise: ?Promise = undefined; initialAndroidNotifHandled = false; openThreadOnceReceived: Set = new Set(); lifecycleSubscription: ?EventSubscription; @@ -135,7 +137,7 @@ ), commIOSNotificationsEventEmitter.addListener( 'remoteNotificationsRegistrationFailed', - this.failedToRegisterPushPermissions, + this.failedToRegisterPushPermissionsIOS, ), commIOSNotificationsEventEmitter.addListener( 'notificationReceivedForeground', @@ -323,9 +325,26 @@ } async ensureAndroidPushNotifsEnabled() { - const hasPermission = await CommAndroidNotifications.hasPermission(); + const permissionPromisesResult = await Promise.all([ + CommAndroidNotifications.hasPermission(), + CommAndroidNotifications.canRequestNotificationsPermissionFromUser(), + ]); + + let [hasPermission] = permissionPromisesResult; + const [, canRequestPermission] = permissionPromisesResult; + + if (!hasPermission && canRequestPermission) { + const permissionResponse = await (async () => { + // We issue a call to sleep to match iOS behavior where prompt + // doesn't appear immediately but after logged-out modal disappears + await sleep(10); + await this.requestAndroidNotificationsPermission(); + })(); + hasPermission = permissionResponse; + } + if (!hasPermission) { - this.failedToRegisterPushPermissions(); + this.failedToRegisterPushPermissionsAndroid(!canRequestPermission); return; } @@ -333,10 +352,22 @@ const fcmToken = await CommAndroidNotifications.getToken(); await this.handleAndroidDeviceToken(fcmToken); } catch (e) { - this.failedToRegisterPushPermissions(); + this.failedToRegisterPushPermissionsAndroid(!canRequestPermission); } } + requestAndroidNotificationsPermission = () => { + if (!this.androidNotificationsPermissionPromise) { + this.androidNotificationsPermissionPromise = (async () => { + const notifPermission = + await CommAndroidNotifications.requestNotificationsPermission(); + this.androidNotificationsPermissionPromise = undefined; + return notifPermission; + })(); + } + return this.androidNotificationsPermissionPromise; + }; + handleAndroidDeviceToken = async (deviceToken: string) => { this.registerPushPermissions(deviceToken); await this.handleInitialAndroidNotification(); @@ -374,15 +405,22 @@ ); } - failedToRegisterPushPermissions = () => { + failedToRegisterPushPermissionsIOS = () => { this.setDeviceToken(null); if (!this.props.loggedIn) { return; } - const deviceType = Platform.OS; - if (deviceType === 'ios') { - iosPushPermissionResponseReceived(); - } else { + iosPushPermissionResponseReceived(); + }; + + failedToRegisterPushPermissionsAndroid = ( + shouldShowAlertOnAndroid: boolean, + ) => { + this.setDeviceToken(null); + if (!this.props.loggedIn) { + return; + } + if (shouldShowAlertOnAndroid) { this.showNotifAlertOnAndroid(); } };