diff --git a/App.tsx b/App.tsx index 4f03242e..29db2845 100644 --- a/App.tsx +++ b/App.tsx @@ -15,7 +15,7 @@ import * as Sentry from '@sentry/react-native'; import codePush from 'react-native-code-push'; import AsyncStorage from '@react-native-async-storage/async-storage'; import CodePush from 'react-native-code-push'; -import store, { persistor } from './src/store/store'; +import store, { persistor, RootState } from './src/store/store'; import { navigationRef } from './src/components/utlis/navigationUtlis'; import FullScreenLoader from './RN-UI-LIB/src/components/FullScreenLoader'; @@ -42,7 +42,7 @@ import { StorageKeys } from './src/types/storageKeys'; import dayJs from 'dayjs'; import { GlobalImageMap, hydrateGlobalImageMap } from './src/common/CachedImage'; import analytics from '@react-native-firebase/analytics'; -import handleUpdatedConfigureValuesFromFirebase from './src/services/firebaseFetchAndUpdate.service'; +import fetchUpdatedRemoteConfig from './src/services/firebaseFetchAndUpdate.service'; import { addClickstreamEvent } from './src/services/clickstreamEventService'; import ScreenshotBlocker from './src/components/utlis/ScreenshotBlocker'; @@ -130,6 +130,7 @@ function App() { }, []); React.useEffect(() => { + fetchUpdatedRemoteConfig(); askForPermissions(); const appStateChange = AppState.addEventListener('change', async (change) => { handleAppStateChange(change); @@ -144,7 +145,6 @@ function App() { setIsGlobalDocumentMapLoaded(true); })(); checkCodePushAndSync(); - handleUpdatedConfigureValuesFromFirebase(); setForegroundTimeStampAndClickstream(); return () => { diff --git a/RN-UI-LIB b/RN-UI-LIB index 99ea2097..64db024f 160000 --- a/RN-UI-LIB +++ b/RN-UI-LIB @@ -1 +1 @@ -Subproject commit 99ea2097a0186866cff21c98200494915088050f +Subproject commit 64db024f2d7de7ccdec44a2c18f3e6df1ffce8e0 diff --git a/android/app/build.gradle b/android/app/build.gradle index 518122c7..8c75e130 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -131,8 +131,8 @@ def reactNativeArchitectures() { return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] } -def VERSION_CODE = 94 -def VERSION_NAME = "2.4.10" +def VERSION_CODE = 95 +def VERSION_NAME = "2.5.0" android { ndkVersion rootProject.ext.ndkVersion diff --git a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java index 478a84f3..c91bb2da 100644 --- a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java +++ b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java @@ -100,6 +100,20 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule { } } + public String getAppIcon(String packageName) { + try { + Context context = RNContext.getApplicationContext(); + PackageManager pm = context.getPackageManager(); + ApplicationInfo appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + + Uri imageUri = Uri.parse("android.resource://" + appInfo.packageName + "/drawable/" + appInfo.icon); + return imageUri.toString(); + + } catch (Exception e) { + return null; + } + } + @ReactMethod public void getAllInstalledApp(Promise promise) { try { @@ -108,13 +122,26 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule { List packages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); JSONArray jsonArray = new JSONArray(); for (PackageInfo packageInfo : installedPackages) { + + final PackageManager pm = RNContext.getApplicationContext().getPackageManager(); + ApplicationInfo appsInstalled; + try { + appsInstalled = pm.getApplicationInfo( packageInfo.packageName, 0); + } catch (final PackageManager.NameNotFoundException e) { + appsInstalled = null; + } + final String applicationName = (String) (appsInstalled != null ? pm.getApplicationLabel(appsInstalled) : "(unknown)"); + JSONObject mainObject = new JSONObject(); - JSONObject appObject = new JSONObject(); - appObject.put("appName", packageInfo.applicationInfo.processName); - appObject.put("firstInstallTime", packageInfo.firstInstallTime); - appObject.put("lastUpdateTime", packageInfo.lastUpdateTime); - mainObject.put("packageName", packageInfo.packageName); - mainObject.put("appDetails", appObject); + JSONObject appDetails = new JSONObject(); + appDetails.put("appName",packageInfo.applicationInfo.processName); + appDetails.put("firstInstallTime", packageInfo.firstInstallTime); + appDetails.put("lastUpdateTime", packageInfo.lastUpdateTime); + appDetails.put("applicationName", applicationName); + appDetails.put("applicationIcon",getAppIcon(packageInfo.packageName)); + mainObject.put("packageName", packageInfo.packageName); + mainObject.put("appDetails", appDetails); + jsonArray.put(mainObject); } promise.resolve(jsonArray.toString()); @@ -292,5 +319,4 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule { - } diff --git a/package.json b/package.json index 3f3d3d68..95ca5eec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "AV_APP", - "version": "2.4.10", + "version": "2.5.0", "private": true, "scripts": { "android:dev": "yarn move:dev && react-native run-android", diff --git a/src/common/BlockerScreen.tsx b/src/common/BlockerScreen.tsx index 3a50fc9e..924872ee 100644 --- a/src/common/BlockerScreen.tsx +++ b/src/common/BlockerScreen.tsx @@ -1,15 +1,22 @@ import React, { ReactNode, useCallback, useState } from 'react'; -import { Linking } from 'react-native'; +import { AppState, Linking } from 'react-native'; import { useSelector } from 'react-redux'; import { RootState } from '../store/store'; import { UninstallInformation } from '../reducer/metadataSlice'; import { getAppVersion } from '../components/utlis/commonFunctions'; import BlockerInstructions from './BlockerInstructions'; -import { BLOCKER_SCREEN_DATA } from './Constants'; +import { BLOCKER_SCREEN_DATA, CLICKSTREAM_EVENT_NAMES } from './Constants'; import { useAppDispatch, useAppSelector } from '../hooks'; import { setIsDeviceLocationEnabled } from '../reducer/foregroundServiceSlice'; import { toast } from '../../RN-UI-LIB/src/components/toast'; import { locationEnabled } from '../components/utlis/DeviceUtils'; +import BlockerScreenApps from '@screens/permissions/BlockerScreenApps'; +import handleBlacklistedAppsForBlockingCosmos, { + Apps, + BLACKLISTED_APPS_LIST, +} from '@services/blacklistedApps.service'; +import { addClickstreamEvent } from '@services/clickstreamEventService'; +import { setBlacklistedAppsInstalledData } from '@reducers/blacklistedAppsInstalledSlice'; interface IBlockerScreen { children?: ReactNode; @@ -32,6 +39,10 @@ const BlockerScreen = (props: IBlockerScreen) => { return state.metadata?.forceUninstall; }); + const blacklistedAppsInstalled: Apps[] = useSelector((state: RootState) => { + return state?.blacklistAppsInstalled?.blacklistedAppsInstalled || []; + }); + function compareSemverVersions(a: string, b: string) { return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }); } @@ -54,6 +65,22 @@ const BlockerScreen = (props: IBlockerScreen) => { setForceReinstallData(undefined); }, [JSON.stringify(forceUninstallData || {})]); + React.useEffect(() => { + handleBlacklistedAppsForBlockingCosmos().then((blacklistedAppsInstalled) => + dispatch( + setBlacklistedAppsInstalledData({ blacklistedAppsInstalled: blacklistedAppsInstalled }) + ) + ); + const appStateChange = AppState.addEventListener('change', async (change) => { + handleBlacklistedAppsForBlockingCosmos().then((blacklistedAppsInstalled) => + dispatch( + setBlacklistedAppsInstalledData({ blacklistedAppsInstalled: blacklistedAppsInstalled }) + ) + ); + }); + return () => appStateChange.remove(); + }, [BLACKLISTED_APPS_LIST]); + const handleDownloadNewApp = () => { if (forceReinstallData?.reinstall_endpoint) { openApkDownloadLink(forceReinstallData?.reinstall_endpoint); @@ -126,6 +153,11 @@ const BlockerScreen = (props: IBlockerScreen) => { ); } + if (blacklistedAppsInstalled?.length > 0) { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_BLOCKER_SCREEN_LOADED_FOR_BLACKLISTED_APPS); + return ; + } + return <>{props.children}; }; diff --git a/src/common/Constants.ts b/src/common/Constants.ts index b44688ee..f857289a 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -638,6 +638,13 @@ export const CLICKSTREAM_EVENT_NAMES = { name: 'FA_PERFORMANCE_DASHBOARD_BROKEN_PTP_CASES_LOAD', description: 'Performance Dashboard Broken PTP Cases Load', }, + + //Blocker Screen for blacklisted Apps + FA_BLOCKER_SCREEN_LOADED_FOR_BLACKLISTED_APPS: { + name: 'FA_BLOCKER_SCREEN_LOADED_FOR_BLACKLISTED_APPS', + description: 'Blocker screen loaded for blacklisted apps', + }, + // Nearby Cases FA_NEARBY_CASES_BUTTON_CLICKED: { name: 'FA_NEARBY_CASES_BUTTON_CLICKED', diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx index 4206a7b4..aa7c085b 100644 --- a/src/common/TrackingComponent.tsx +++ b/src/common/TrackingComponent.tsx @@ -45,6 +45,11 @@ import { GlobalImageMap } from './CachedImage'; import { get } from 'react-hook-form'; import { addClickstreamEvent } from '../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from './Constants'; +import { setBlacklistedAppsInstalledData } from '@reducers/blacklistedAppsInstalledSlice'; +import handleBlacklistedAppsForBlockingCosmos, { Apps } from '@services/blacklistedApps.service'; +import fetchUpdatedRemoteConfig, { + FIREBASE_FETCH_TIMESTAMP, +} from '@services/firebaseFetchAndUpdate.service'; export enum FOREGROUND_TASKS { GEOLOCATION = 'GEOLOCATION', @@ -54,6 +59,7 @@ export enum FOREGROUND_TASKS { UPDATE_AGENT_ACTIVENESS = 'UPDATE_AGENT_ACTIVENESS', UPDATE_AGENT_ACTIVITY = 'UPDATE_AGENT_ACTIVITY', DELETE_CACHE = 'DELETE_CACHE', + FETCH_DATA_FROM_FIREBASE = 'FETCH_DATA_FROM_FIREBASE', } interface ITrackingComponent { @@ -106,6 +112,14 @@ const TrackingComponent: React.FC = ({ children }) => { const userActivityonApp: string = (await getItem(StorageKeys.USER_ACTIVITY_ON_APP)) || AgentActivity.LOW; + let blacklistedAppsInstalledOnDevice: Apps[] = []; + + await handleBlacklistedAppsForBlockingCosmos().then((blacklistedAppsInstalled) => { + if (blacklistedAppsInstalled.length > 0) { + blacklistedAppsInstalledOnDevice = blacklistedAppsInstalled; + } + }); + const geolocation: IGeolocationPayload = { latitude: location.latitude, longitude: location.longitude, @@ -113,6 +127,7 @@ const TrackingComponent: React.FC = ({ children }) => { timestamp: Date.now(), isActiveOnApp: Boolean(isActiveOnApp), userActivityOnApp: String(userActivityonApp), + blacklistedAppsInstalled: blacklistedAppsInstalledOnDevice, }; dispatch(setDeviceGeolocationsBuffer(geolocation)); dispatch(sendLocationAndActivenessToServer([geolocation])); @@ -289,6 +304,16 @@ const TrackingComponent: React.FC = ({ children }) => { }); }; + const handleFetchUpdatedDataFromFirebase = async () => { + const currentTimestamp: number = Date.now(); + if ( + FIREBASE_FETCH_TIMESTAMP && + currentTimestamp - FIREBASE_FETCH_TIMESTAMP > 15 * MILLISECONDS_IN_A_MINUTE + ) { + fetchUpdatedRemoteConfig(); + } + }; + const tasks: IForegroundTask[] = [ { taskId: FOREGROUND_TASKS.TIME_SYNC, @@ -320,6 +345,12 @@ const TrackingComponent: React.FC = ({ children }) => { delay: DATA_SYNC_TIME_INTERVAL, onLoop: true, }, + { + taskId: FOREGROUND_TASKS.FETCH_DATA_FROM_FIREBASE, + task: handleFetchUpdatedDataFromFirebase, + delay: 15 * MILLISECONDS_IN_A_MINUTE, // 15 minutes + onLoop: true, + }, ]; if (!isTeamLead) { @@ -374,6 +405,11 @@ const TrackingComponent: React.FC = ({ children }) => { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_FOREGROUND, { now }); handleGetCaseSyncStatus(); dispatch(getConfigData()); + handleBlacklistedAppsForBlockingCosmos().then((blacklistedAppsInstalled) => + dispatch( + setBlacklistedAppsInstalledData({ blacklistedAppsInstalled: blacklistedAppsInstalled }) + ) + ); CosmosForegroundService.start(tasks); } if (nextAppState === AppStates.BACKGROUND) { @@ -392,6 +428,11 @@ const TrackingComponent: React.FC = ({ children }) => { } await handleGetCaseSyncStatus(); dispatch(getConfigData()); + handleBlacklistedAppsForBlockingCosmos().then((blacklistedAppsInstalled) => + dispatch( + setBlacklistedAppsInstalledData({ blacklistedAppsInstalled: blacklistedAppsInstalled }) + ) + ); if (!isTeamLead && LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES) { const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId); if (updatedDetails?.cases?.length) { diff --git a/src/hooks/capturingApi.ts b/src/hooks/capturingApi.ts index 4c32cc1b..e411b9e6 100644 --- a/src/hooks/capturingApi.ts +++ b/src/hooks/capturingApi.ts @@ -6,6 +6,7 @@ import { } from '../reducer/foregroundServiceSlice'; import { logError } from '../components/utlis/errorUtils'; import { toast } from '../../RN-UI-LIB/src/components/toast'; +import { Apps } from '@services/blacklistedApps.service'; export interface IGeolocationPayload { latitude: number; @@ -14,6 +15,7 @@ export interface IGeolocationPayload { timestamp: number; isActiveOnApp: boolean; userActivityOnApp: string; + blacklistedAppsInstalled: Apps[]; } export const sendLocationAndActivenessToServer = diff --git a/src/reducer/blacklistedAppsInstalledSlice.ts b/src/reducer/blacklistedAppsInstalledSlice.ts new file mode 100644 index 00000000..89eee0f5 --- /dev/null +++ b/src/reducer/blacklistedAppsInstalledSlice.ts @@ -0,0 +1,21 @@ +import { createSlice } from '@reduxjs/toolkit'; +import handleBlacklistedAppsForBlockingCosmos, { Apps } from '@services/blacklistedApps.service'; + +const initialState = { + blacklistedAppsInstalled: [] as Apps[], +}; + +export const blacklistedAppsInstalledSlice = createSlice({ + name: 'blacklistedAppsInstalled', + initialState, + reducers: { + setBlacklistedAppsInstalledData: (state, action) => { + const { blacklistedAppsInstalled } = action.payload; + state.blacklistedAppsInstalled = blacklistedAppsInstalled; + }, + }, +}); + +export const { setBlacklistedAppsInstalledData } = blacklistedAppsInstalledSlice.actions; + +export default blacklistedAppsInstalledSlice.reducer; diff --git a/src/screens/auth/AuthRouter.tsx b/src/screens/auth/AuthRouter.tsx index 868f28b9..a5ae6d6d 100644 --- a/src/screens/auth/AuthRouter.tsx +++ b/src/screens/auth/AuthRouter.tsx @@ -108,7 +108,9 @@ const AuthRouter = () => { ) : ( - + + + ); }; diff --git a/src/screens/permissions/BlockerScreenApps.tsx b/src/screens/permissions/BlockerScreenApps.tsx new file mode 100644 index 00000000..045833d3 --- /dev/null +++ b/src/screens/permissions/BlockerScreenApps.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { View, StyleSheet, ScrollView, Image } from 'react-native'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { COLORS } from '@rn-ui-lib/colors'; +import PermissionImage from '@assets/images/PermissionImage'; +import { Apps } from '@services/blacklistedApps.service'; +import Text from '@rn-ui-lib/components/Text'; + +const BlockerScreenApps: React.FC<{ blacklistedAppsInstalled: Apps[] }> = ({ + blacklistedAppsInstalled, +}) => { + return ( + + + + + + + + Some installed apps are banned by Navi + + + + + + Uninstall these apps for uninterrupted usage + + + + + + {blacklistedAppsInstalled.map((app: Apps, index: number) => ( + + + + {app.applicationName} + + {index < blacklistedAppsInstalled.length - 1 && ( + + )} + + ))} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'column', + flex: 1, + backgroundColor: COLORS.BACKGROUND.PRIMARY, + }, + + imageContainer: { + marginLeft: 97, + marginTop: 40, + marginBottom: 37, + }, + + textDark: { + color: COLORS.TEXT.DARK, + fontWeight: '500', + }, + + textLight: { + color: COLORS.TEXT.LIGHT, + fontWeight: '400', + marginBottom: 14, + }, + + appsContainer: { + paddingHorizontal: 16, + paddingTop: 4, + borderRadius: 4, + marginBottom: 16, + backgroundColor: COLORS.BACKGROUND.SILVER_LIGHT, + }, + + appsListItem: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 8, + paddingVertical: 12, + }, + + appNameText: { + marginLeft: 8, + fontSize: 12, + }, + + appIcon: { + width: 35, + height: 35, + backgroundColor: COLORS.BACKGROUND.PRIMARY, + borderRadius: 20, + }, + + horizontalLine: { + backgroundColor: COLORS.BACKGROUND.SILVER_LIGHT_2, + width: '100%', + height: 1, + }, +}); + +export default BlockerScreenApps; diff --git a/src/services/blacklistedApps.service.ts b/src/services/blacklistedApps.service.ts new file mode 100644 index 00000000..f3f87855 --- /dev/null +++ b/src/services/blacklistedApps.service.ts @@ -0,0 +1,68 @@ +import { GenericType } from '@common/GenericTypes'; +import { getAllInstalledApp } from '@components/utlis/DeviceUtils'; +import { logError } from '@components/utlis/errorUtils'; + +export type Apps = { + packageName: string; + applicationName: string; + applicationIcon: string; +}; + +type deviceApps = { + packageName: string; + appDetails: deviceAppDetails; +}; + +type deviceAppDetails = { + applicationName: string; + applicationIcon: string; +}; + +export let BLACKLISTED_APPS_LIST: string[] = []; +let installedBlacklistedApps: Apps[] = []; + +export const getBlacklistedAppsList = () => BLACKLISTED_APPS_LIST; + +export const setBlacklistedAppsList = (blacklistedAppsString: string) => { + BLACKLISTED_APPS_LIST = blacklistedAppsString.split(','); +}; + +function getBlacklistAppsPresent(installedApps: Apps[], blacklistedApps: string[]) { + installedBlacklistedApps = []; + const blacklistedAppsSet = new Set(blacklistedApps); + for (const app of installedApps) { + if (blacklistedAppsSet.has(app.packageName)) { + installedBlacklistedApps.push({ + packageName: app.packageName, + applicationName: app.applicationName, + applicationIcon: app.applicationIcon, + }); + } + } +} + +const handleBlacklistedAppsForBlockingCosmos = async () => { + const blacklistedApps = getBlacklistedAppsList(); + return getAllInstalledApp() + .then((apps) => { + try { + const appsArray = JSON.parse(apps); + const installedApps = appsArray.map((app: deviceApps) => ({ + packageName: app.packageName, + applicationName: app.appDetails.applicationName, + applicationIcon: app.appDetails.applicationIcon, + })); + getBlacklistAppsPresent(installedApps, blacklistedApps); + return installedBlacklistedApps; + } catch (error: GenericType) { + logError(error); + return []; + } + }) + .catch((error) => { + logError(error); + return []; + }); +}; + +export default handleBlacklistedAppsForBlockingCosmos; diff --git a/src/services/firebaseFetchAndUpdate.service.ts b/src/services/firebaseFetchAndUpdate.service.ts index 1816fb35..eddb8706 100644 --- a/src/services/firebaseFetchAndUpdate.service.ts +++ b/src/services/firebaseFetchAndUpdate.service.ts @@ -4,9 +4,11 @@ import { setActivityTimeWindowHigh, setActivityTimeWindowMedium, } from '../common/AgentActivityConfigurableConstants'; +import { setBlacklistedAppsList } from './blacklistedApps.service'; const FIREBASE_FETCH_TIME = 15 * 60; -async function handleUpdatedConfigureValuesFromFirebase() { +export let FIREBASE_FETCH_TIMESTAMP: number; +async function fetchUpdatedRemoteConfig() { await remoteConfig().fetch(FIREBASE_FETCH_TIME); //15 minutes remoteConfig() .activate() @@ -28,10 +30,13 @@ async function handleUpdatedConfigureValuesFromFirebase() { const ACTIVITY_TIME_WINDOW_MEDIUM = remoteConfig() .getValue('ACTIVITY_TIME_WINDOW_MEDIUM') .asNumber(); + const BLACKLISTED_APPS = remoteConfig().getValue('BLACKLISTED_APPS').asString(); setActivityTimeOnApp(ACTIVITY_TIME_ON_APP); setActivityTimeWindowHigh(ACTIVITY_TIME_WINDOW_HIGH); setActivityTimeWindowMedium(ACTIVITY_TIME_WINDOW_MEDIUM); + setBlacklistedAppsList(BLACKLISTED_APPS); + FIREBASE_FETCH_TIMESTAMP = Date.now(); }); } -export default handleUpdatedConfigureValuesFromFirebase; +export default fetchUpdatedRemoteConfig; diff --git a/src/store/store.ts b/src/store/store.ts index 43ee523f..44cca84a 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -31,6 +31,7 @@ import feedbackImagesSlice from '../reducer/feedbackImagesSlice'; import configSlice from '../reducer/configSlice'; import profileSlice from '../reducer/profileSlice'; import reporteesSlice from '../reducer/reporteesSlice'; +import blacklistedAppsInstalledSlice from '@reducers/blacklistedAppsInstalledSlice'; import feedbackFiltersSlice from '@reducers/feedbackFiltersSlice'; import agentPerformanceSlice from '../reducer/agentPerformanceSlice'; @@ -54,6 +55,7 @@ const rootReducer = combineReducers({ config: configSlice, profile: profileSlice, reportees: reporteesSlice, + blacklistAppsInstalled: blacklistedAppsInstalledSlice, feedbackFilters: feedbackFiltersSlice, agentPerformance: agentPerformanceSlice, });