diff --git a/package/src/expo-plugin/@types.ts b/package/src/expo-plugin/@types.ts index 015901617c..939bb41793 100644 --- a/package/src/expo-plugin/@types.ts +++ b/package/src/expo-plugin/@types.ts @@ -47,4 +47,26 @@ export type ConfigProps = { * @default false */ enableCodeScanner?: boolean + /** + * Android Only + * Whether to require the camera through Androids uses-feature flag. + * + * When set to `true`, the app will only be available in Play Store for phones with a camera. + * When set to `false`, the feature will not be required, and the app will be discoverable for devices without a camera. + * If it is `undefined` or `null`, the uses-feature flag will not be added to the AndroidManifest. + * + * @default undefined + */ + requiresCamera?: boolean | null + /** + * Android Only + * Whether to require the microphone through Androids uses-feature flag. + * + * When set to `true`, the app will only be available in Play Store for phones with a microphone. + * When set to `false`, the feature will not be required, and the app will be discoverable for devices without a microphone. + * If it is `undefined` or `null`, the uses-feature flag will not be added to the AndroidManifest. + * + * @default undefined + */ + requiresMicrophone?: boolean | null } diff --git a/package/src/expo-plugin/withUsesFeatureAndroid.ts b/package/src/expo-plugin/withUsesFeatureAndroid.ts new file mode 100644 index 0000000000..8aebeb5bde --- /dev/null +++ b/package/src/expo-plugin/withUsesFeatureAndroid.ts @@ -0,0 +1,19 @@ +import type { ConfigPlugin } from '@expo/config-plugins' +import { withAndroidManifest } from '@expo/config-plugins' + +export const withUsesFeatureAndroid: ConfigPlugin<[string, boolean]> = (c, [featureName, featureIsRequired]) => { + return withAndroidManifest(c, (config) => { + if (!Array.isArray(config.modResults.manifest['uses-feature'])) config.modResults.manifest['uses-feature'] = [] + + if (config.modResults.manifest['uses-feature'].find((item) => item.$['android:name'] === featureName) == null) { + config.modResults.manifest['uses-feature'].push({ + $: { + 'android:name': featureName, + 'android:required': featureIsRequired ? 'true' : 'false', + }, + }) + } + + return config + }) +} diff --git a/package/src/expo-plugin/withVisionCamera.ts b/package/src/expo-plugin/withVisionCamera.ts index 13d0034ee2..7d444c313e 100644 --- a/package/src/expo-plugin/withVisionCamera.ts +++ b/package/src/expo-plugin/withVisionCamera.ts @@ -3,6 +3,7 @@ import { withPlugins, AndroidConfig, createRunOncePlugin } from '@expo/config-pl import { withEnableFrameProcessorsAndroid } from './withEnableFrameProcessorsAndroid' import { withEnableFrameProcessorsIOS } from './withEnableFrameProcessorsIOS' import { withAndroidMLKitVisionModel } from './withAndroidMLKitVisionModel' +import { withUsesFeatureAndroid } from './withUsesFeatureAndroid' import type { ConfigProps } from './@types' import { withEnableLocationIOS } from './withEnableLocationIOS' // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment @@ -41,6 +42,14 @@ const withCamera: ConfigPlugin = (config, props = {}) => { config = withEnableFrameProcessorsAndroid(config, props.enableFrameProcessors) config = withEnableFrameProcessorsIOS(config, props.enableFrameProcessors) } + if (props.requiresCamera !== null && props.requiresCamera !== undefined) { + // add uses-feature element to AndroidManifest.xml for hiding app from Play store in case camera is not available on the device + config = withUsesFeatureAndroid(config, ['android.hardware.camera', props.requiresCamera]) + } + if (props.requiresMicrophone !== null && props.requiresMicrophone !== undefined) { + // add uses-feature element to AndroidManifest.xml for hiding app from Play store in case microphone is not available on the device + config = withUsesFeatureAndroid(config, ['android.hardware.microphone', props.requiresMicrophone]) + } if (props.enableCodeScanner) config = withAndroidMLKitVisionModel(config, props)