Skip to content

Commit

Permalink
Iss 22846 add runtime permission flow on android (#1407)
Browse files Browse the repository at this point in the history
### Pre-launch Checklist

- [x] The [Documentation] is updated accordingly, or this PR doesn't
require it.
- [x] I have updated the `ExampleAppChangelog.txt` file with relevant
changes.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making, or this PR is
test-exempt.
- [x] All existing and new tests are passing.

<!-- Links -->

[Documentation]: https://www.100ms.live/docs

---------

Co-authored-by: ygit <[email protected]>
Co-authored-by: Yogesh Singh <[email protected]>
  • Loading branch information
3 people authored Jul 17, 2024
1 parent 0b28db6 commit 6ad3bb4
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,23 @@ class HMSManager(
}
// endregion

@ReactMethod
fun setPermissionsAccepted(
data: ReadableMap,
promise: Promise?,
) {
val rnSDK =
HMSHelper.getHms(data, hmsCollection) ?: run {
promise?.reject(
"6004",
"RN HMS SDK not initialized",
)
return
}
rnSDK.hmsSDK?.setPermissionsAccepted()
promise?.resolve(null)
}

// region Warning on JS side
@ReactMethod
fun addListener(eventName: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ class HMSRNSDK(
}
}

if (data?.hasKey("haltPreviewJoinForPermissionsRequest") == true) {
val halt = data?.getBoolean("haltPreviewJoinForPermissionsRequest")
if (halt != null) {
builder.haltPreviewJoinForPermissionsRequest(halt)
}
}

this.hmsSDK = builder.build()

hmsSDK?.let {
Expand Down Expand Up @@ -286,6 +293,17 @@ class HMSRNSDK(
data.putArray("removedPeers", removedPeersArray)
delegate.emitEvent("ON_PEER_LIST_UPDATED", data)
}

override fun onPermissionsRequested(permissions: List<String>) {
if (eventsEnableStatus["ON_PERMISSIONS_REQUESTED"] != true) {
return
}
val data: WritableMap = Arguments.createMap()

data.putArray("permissions", Arguments.fromList(permissions))
data.putString("id", id)
delegate.emitEvent("ON_PERMISSIONS_REQUESTED", data)
}
},
)
} else {
Expand Down Expand Up @@ -557,6 +575,17 @@ class HMSRNSDK(
data.putString("id", id)
delegate.emitEvent("ON_TRANSCRIPTS", data)
}

override fun onPermissionsRequested(permissions: List<String>) {
if (eventsEnableStatus["ON_PERMISSIONS_REQUESTED"] != true) {
return
}
val data: WritableMap = Arguments.createMap()

data.putArray("permissions", Arguments.fromList(permissions))
data.putString("id", id)
delegate.emitEvent("ON_PERMISSIONS_REQUESTED", data)
}
},
)

Expand Down
67 changes: 66 additions & 1 deletion packages/react-native-hms/src/classes/HMSSDK.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { HMSRole } from './HMSRole';
import type { HMSTrack } from './HMSTrack';
import type { HMSLogger } from './HMSLogger';
import type { HMSPeer } from './HMSPeer';
import type { HMSVideoViewMode } from './HMSVideoViewMode';
import type { HMSTrackSettings } from './HMSTrackSettings';
import type { HMSRTMPConfig } from './HMSRTMPConfig';
import type { HMSHLSConfig } from './HMSHLSConfig';
Expand Down Expand Up @@ -74,6 +75,7 @@ export class HMSSDK {
private appStateSubscription?: any;
private onPreviewDelegate?: any;
private onJoinDelegate?: any;
private onPermissionsRequestedDelegate?: any;
private onRoomDelegate?: any;
private onTranscriptsDelegate?: any;
private onPeerDelegate?: any;
Expand Down Expand Up @@ -140,11 +142,14 @@ export class HMSSDK {
* });
*
* @see https://www.100ms.live/docs/react-native/v2/how-to-guides/install-the-sdk/hmssdk
* @static async build - Asynchronously builds and returns an instance of the HMSSDK class.
* @static
* @async
* @function build
* @memberof HMSSDK
*/
static async build(params?: {
trackSettings?: HMSTrackSettings;
haltPreviewJoinForPermissionsRequest?: boolean;
appGroup?: String;
preferredExtension?: String;
logSettings?: HMSLogSettings;
Expand All @@ -154,6 +159,8 @@ export class HMSSDK {
const { major, minor, patch } = ReactNativeVersion.version;
let id = await HMSManager.build({
trackSettings: params?.trackSettings,
haltPreviewJoinForPermissionsRequest:
params?.haltPreviewJoinForPermissionsRequest, // required for Android Permissions, not required for iOS
appGroup: params?.appGroup, // required for iOS Screenshare, not required for Android
preferredExtension: params?.preferredExtension, // required for iOS Screenshare, not required for Android
frameworkInfo: {
Expand Down Expand Up @@ -1520,6 +1527,12 @@ export class HMSSDK {
return HMSManager.setAlwaysScreenOn({ id: this.id, enabled });
};

setPermissionsAccepted = async () => {
if (Platform.OS === 'ios') return;
logger?.verbose('#Function setPermissionsAccepted', { id: this.id });
return HMSManager.setPermissionsAccepted({ id: this.id });
};

/**
* - This is a prototype event listener that takes action and listens for updates related to that particular action
*
Expand Down Expand Up @@ -1568,6 +1581,28 @@ export class HMSSDK {
this.onJoinDelegate = callback;
break;
}
case HMSUpdateListenerActions.ON_PERMISSIONS_REQUESTED: {
// Checking if we already have ON_PERMISSIONS_REQUESTED subscription
if (
!this.emitterSubscriptions[
HMSUpdateListenerActions.ON_PERMISSIONS_REQUESTED
]
) {
// Adding ON_PERMISSIONS_REQUESTED native listener
const permissionsRequestedSubscription =
HMSNativeEventListener.addListener(
this.id,
HMSUpdateListenerActions.ON_PERMISSIONS_REQUESTED,
this.onPermissionsRequestedListener
);
this.emitterSubscriptions[
HMSUpdateListenerActions.ON_PERMISSIONS_REQUESTED
] = permissionsRequestedSubscription;
}
// Adding App Delegate listener
this.onPermissionsRequestedDelegate = callback;
break;
}
case HMSUpdateListenerActions.ON_ROOM_UPDATE: {
// Checking if we already have ON_ROOM_UPDATE subscription
if (
Expand Down Expand Up @@ -2031,6 +2066,23 @@ export class HMSSDK {
this.onJoinDelegate = null;
break;
}
case HMSUpdateListenerActions.ON_PERMISSIONS_REQUESTED: {
const subscription =
this.emitterSubscriptions[
HMSUpdateListenerActions.ON_PERMISSIONS_REQUESTED
];
// Removing ON_PERMISSIONS_REQUESTED native listener
if (subscription) {
subscription.remove();

this.emitterSubscriptions[
HMSUpdateListenerActions.ON_PERMISSIONS_REQUESTED
] = undefined;
}
// Removing App Delegate listener
this.onPermissionsRequestedDelegate = null;
break;
}
case HMSUpdateListenerActions.ON_ROOM_UPDATE: {
const subscription =
this.emitterSubscriptions[HMSUpdateListenerActions.ON_ROOM_UPDATE];
Expand Down Expand Up @@ -2431,6 +2483,19 @@ export class HMSSDK {
}
};

onPermissionsRequestedListener = (data: {
id: string;
permissions: Array<string>;
}) => {
if (data.id !== this.id) {
return;
}
if (this.onPermissionsRequestedDelegate) {
logger?.verbose('#Listener ON_PERMISSIONS_REQUESTED_LISTENER_CALL', data);
this.onPermissionsRequestedDelegate({ ...data });
}
};

onRoomListener = (data: any) => {
if (data.id !== this.id) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
* @property {string} ON_SESSION_STORE_CHANGED - Emitted when the session store has changed.
* @property {string} ON_PEER_LIST_UPDATED - Emitted when the list of peers is updated.
* @property {string} ON_TRANSCRIPTS - Emitted when transcripts are available.
*
* @property {string} ON_PERMISSIONS_REQUESTED - Emitted when permissions are requested.
*/
export enum HMSUpdateListenerActions {
ON_PREVIEW = 'ON_PREVIEW',
Expand All @@ -57,5 +57,28 @@ export enum HMSUpdateListenerActions {
ON_SESSION_STORE_AVAILABLE = 'ON_SESSION_STORE_AVAILABLE',
ON_SESSION_STORE_CHANGED = 'ON_SESSION_STORE_CHANGED',
ON_PEER_LIST_UPDATED = 'ON_PEER_LIST_UPDATED',

/**
* Event emitted when transcripts are available.
*
* This event is triggered when the HMS SDK has generated transcripts from the audio streams in the room.
* It allows the application to receive real-time or post-processed text versions of spoken content, which can be used for
* accessibility features, content analysis, or storing meeting minutes. The availability of this feature depends on the
* HMS service configuration and may require additional setup or permissions.
*
* @type {string}
* @see https://www.100ms.live/docs/react-native/v2/how-to-guides/extend-capabilities/live-captions
*/
ON_TRANSCRIPTS = 'ON_TRANSCRIPTS',

/**
* Event emitted when the HMS SDK requests permissions from the user. Android only.
*
* This event is triggered whenever the application needs to request permissions from the user, such as access to the camera or microphone.
* It is used in conjunction with the platform's permissions API to prompt the user for the necessary permissions and to inform the HMS SDK
* of the user's response. This is crucial for features that require explicit user consent before they can be used.
*
* @type {string}
*/
ON_PERMISSIONS_REQUESTED = 'ON_PERMISSIONS_REQUESTED',
}
11 changes: 10 additions & 1 deletion packages/react-native-room-kit/example/ExampleAppChangelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Board: https://app.devrev.ai/100ms/vistas/vista-254

- Add runtime permission flow on Android
https://app.devrev.ai/100ms/works/ISS-22855

- Resolve app not running on Android 14 issue
https://app.devrev.ai/100ms/works/ISS-22860

- Resolve warning about React State update
https://app.devrev.ai/100ms/works/ISS-22807

Room Kit: 1.2.2
React Native SDK: 1.10.9
Android SDK: 2.9.63
Android SDK: 2.9.62
iOS SDK: 1.14.1
62 changes: 25 additions & 37 deletions packages/react-native-room-kit/example/src/utils/functions.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { Platform } from 'react-native';
import {Platform} from 'react-native';

import {
PERMISSIONS,
request,
requestMultiple,
RESULTS,
} from 'react-native-permissions';
import { getRoomLinkDetails } from './getRoomLinkDetails';
import {getRoomLinkDetails} from './getRoomLinkDetails';

export const getMeetingUrl = () =>
'https://reactnative.app.100ms.live/meeting/rlk-lsml-aiy';

export const callService = async (
roomID: string,
success: Function,
failure: Function
failure: Function,
) => {
let roomCode;
let subdomain;
try {
if (validateUrl(roomID)) {
const { roomCode: code, roomDomain: domain } = getRoomLinkDetails(roomID);
const {roomCode: code, roomDomain: domain} = getRoomLinkDetails(roomID);
roomCode = code;
subdomain = domain;

Expand All @@ -33,29 +33,17 @@ export const callService = async (
return;
}

const permissions = await checkPermissions([
PERMISSIONS.ANDROID.CAMERA,
PERMISSIONS.ANDROID.RECORD_AUDIO,
PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
]);

if (permissions) {
const userId = getRandomUserId(6);
const isQARoom = subdomain && subdomain.search('.qa-') >= 0;
success(
roomCode,
userId,
isQARoom
? `https://auth-nonprod.100ms.live${Platform.OS === 'ios' ? '/' : ''}`
: undefined, // Auth Endpoint
isQARoom ? 'https://qa-init.100ms.live/init' : undefined, // HMSConfig Endpoint
isQARoom ? 'https://api-nonprod.100ms.live' : undefined // Room Layout endpoint
);
return;
} else {
failure('permission not granted');
return;
}
const userId = getRandomUserId(6);
const isQARoom = subdomain && subdomain.search('.qa-') >= 0;
success(
roomCode,
userId,
isQARoom
? `https://auth-nonprod.100ms.live${Platform.OS === 'ios' ? '/' : ''}`
: undefined, // Auth Endpoint
isQARoom ? 'https://qa-init.100ms.live/init' : undefined, // HMSConfig Endpoint
isQARoom ? 'https://api-nonprod.100ms.live' : undefined, // Room Layout endpoint
);
} catch (error) {
console.log(error);
failure('error in call service');
Expand All @@ -73,7 +61,7 @@ export const getRandomNumberInRange = (min: number, max: number) => {
};

export const getRandomUserId = (length: number) => {
return Array.from({ length }, () => {
return Array.from({length}, () => {
const randomAlphaAsciiCode = getRandomNumberInRange(97, 123); // 97 - 122 is the ascii code range for a-z chars
const alphaCharacter = String.fromCharCode(randomAlphaAsciiCode);
return alphaCharacter;
Expand All @@ -89,7 +77,7 @@ export const validateUrl = (url?: string): boolean => {
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
'(\\?[;&a-z\\d%_.~+=-]*)?' +
'(\\#[-a-z\\d_]*)?$',
'i'
'i',
);
return pattern.test(url);
}
Expand All @@ -99,16 +87,16 @@ export const validateUrl = (url?: string): boolean => {
export const checkPermissions = async (
permissions: Array<
(typeof PERMISSIONS.ANDROID)[keyof typeof PERMISSIONS.ANDROID]
>
>,
): Promise<boolean> => {
if (Platform.OS === 'ios') {
return true;
}

try {
const requiredPermissions = permissions.filter(
(permission) =>
permission.toString() !== PERMISSIONS.ANDROID.BLUETOOTH_CONNECT
permission =>
permission.toString() !== PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
);

const results = await requestMultiple(requiredPermissions);
Expand All @@ -121,22 +109,22 @@ export const checkPermissions = async (
console.log(
requiredPermissions[permission],
':',
results[requiredPermissions[permission]]
results[requiredPermissions[permission]],
);
}

// Bluetooth Connect Permission handling
if (
permissions.findIndex(
(permission) =>
permission.toString() === PERMISSIONS.ANDROID.BLUETOOTH_CONNECT
permission =>
permission.toString() === PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
) >= 0
) {
const bleConnectResult = await request(
PERMISSIONS.ANDROID.BLUETOOTH_CONNECT
PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
);
console.log(
`${PERMISSIONS.ANDROID.BLUETOOTH_CONNECT} : ${bleConnectResult}`
`${PERMISSIONS.ANDROID.BLUETOOTH_CONNECT} : ${bleConnectResult}`,
);
}

Expand Down
Loading

0 comments on commit 6ad3bb4

Please sign in to comment.