Skip to content

Commit 80d366b

Browse files
Added new arc support for lib
1 parent b4eb72c commit 80d366b

12 files changed

Lines changed: 158008 additions & 14151 deletions

File tree

.yarn/releases/yarn-1.22.21.cjs

Lines changed: 147513 additions & 0 deletions
Large diffs are not rendered by default.

.yarn/releases/yarn-3.6.1.cjs

Lines changed: 0 additions & 874 deletions
This file was deleted.

.yarnrc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ plugins:
77
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
88
spec: "@yarnpkg/plugin-workspace-tools"
99

10-
yarnPath: .yarn/releases/yarn-3.6.1.cjs
10+
yarnPath: .yarn/releases/yarn-1.22.21.cjs

android/build.gradle

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ def getExtOrIntegerDefault(name) {
3333
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["UsageStatsManager_" + name]).toInteger()
3434
}
3535

36+
// Codegen configuration for New Architecture (TurboModules)
37+
if (isNewArchitectureEnabled()) {
38+
react {
39+
jsRootDir = file("../src")
40+
libraryName = "JustDiceReactNativeUsageStats"
41+
codegenJavaPackageName = "io.justdice.usagestats"
42+
}
43+
}
44+
3645
def supportsNamespace() {
3746
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
3847
def major = parsed[0].toInteger()
@@ -58,7 +67,21 @@ android {
5867
defaultConfig {
5968
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
6069
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
70+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
71+
}
6172

73+
buildFeatures {
74+
buildConfig true
75+
}
76+
77+
sourceSets {
78+
main {
79+
if (isNewArchitectureEnabled()) {
80+
java.srcDirs += ["src/newArch/java"]
81+
} else {
82+
java.srcDirs += ["src/oldArch/java"]
83+
}
84+
}
6285
}
6386

6487
buildTypes {
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
package io.justdice.usagestats
2+
3+
import android.annotation.TargetApi
4+
import android.app.AppOpsManager
5+
import android.app.AppOpsManager.MODE_ALLOWED
6+
import android.app.AppOpsManager.OPSTR_GET_USAGE_STATS
7+
import android.app.usage.EventStats
8+
import android.app.usage.NetworkStats
9+
import android.app.usage.NetworkStatsManager
10+
import android.app.usage.UsageEvents
11+
import android.app.usage.UsageStats
12+
import android.app.usage.UsageStatsManager
13+
import android.content.Context
14+
import android.content.Intent
15+
import android.content.pm.PackageManager
16+
import android.net.ConnectivityManager
17+
import android.net.Uri
18+
import android.os.Build
19+
import android.os.Process
20+
import android.os.RemoteException
21+
import androidx.annotation.RequiresApi
22+
import com.facebook.react.bridge.Promise
23+
import com.facebook.react.bridge.ReactApplicationContext
24+
import com.facebook.react.bridge.WritableMap
25+
import com.facebook.react.bridge.WritableNativeArray
26+
import com.facebook.react.bridge.WritableNativeMap
27+
import com.facebook.react.common.MapBuilder
28+
import io.justdice.usagestats.model.AppData
29+
import io.justdice.usagestats.utils.UsageUtils
30+
import io.justdice.usagestats.utils.UsageUtils.humanReadableMillis
31+
32+
/**
33+
* Shared implementation class containing all native logic.
34+
* Not a React module itself — logic is exposed through the arch-specific wrappers.
35+
*/
36+
class UsageStatsManagerModuleImpl(private val reactContext: ReactApplicationContext) {
37+
38+
@RequiresApi(Build.VERSION_CODES.M)
39+
private var networkStatsManager: NetworkStatsManager? =
40+
reactContext.getSystemService(Context.NETWORK_STATS_SERVICE) as NetworkStatsManager
41+
42+
fun getConstants(): Map<String, Any>? {
43+
val constants: MutableMap<String, Any> = MapBuilder.newHashMap()
44+
constants["INTERVAL_WEEKLY"] = UsageStatsManager.INTERVAL_WEEKLY
45+
constants["INTERVAL_MONTHLY"] = UsageStatsManager.INTERVAL_MONTHLY
46+
constants["INTERVAL_YEARLY"] = UsageStatsManager.INTERVAL_YEARLY
47+
constants["INTERVAL_DAILY"] = UsageStatsManager.INTERVAL_DAILY
48+
constants["INTERVAL_BEST"] = UsageStatsManager.INTERVAL_BEST
49+
constants["TYPE_WIFI"] = ConnectivityManager.TYPE_WIFI
50+
constants["TYPE_MOBILE"] = ConnectivityManager.TYPE_MOBILE
51+
constants["TYPE_MOBILE_AND_WIFI"] = Int.MAX_VALUE
52+
return constants
53+
}
54+
55+
private fun packageExists(packageName: String): Boolean {
56+
return try {
57+
reactContext.packageManager.getApplicationInfo(packageName, 0)
58+
true
59+
} catch (e: PackageManager.NameNotFoundException) {
60+
false
61+
}
62+
}
63+
64+
fun showUsageAccessSettings(packageName: String) {
65+
val intent = Intent(android.provider.Settings.ACTION_USAGE_ACCESS_SETTINGS)
66+
if (packageExists(packageName)) {
67+
intent.data = Uri.fromParts("package", packageName, null)
68+
}
69+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
70+
reactContext.startActivity(intent)
71+
}
72+
73+
// interval is Double to match codegen spec (TurboModules codegen uses Double for numeric types)
74+
fun queryUsageStats(interval: Double, startTime: Double, endTime: Double, promise: Promise) {
75+
val packageManager: PackageManager = reactContext.packageManager
76+
val result: WritableNativeArray = WritableNativeArray()
77+
val usageStatsManager =
78+
reactContext.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
79+
val queryUsageStats: List<UsageStats> =
80+
usageStatsManager.queryUsageStats(interval.toInt(), startTime.toLong(), endTime.toLong())
81+
82+
for (us in queryUsageStats) {
83+
// On API 29+ totalTimeInForeground is always 0 due to OS restrictions.
84+
// Use totalTimeVisible (API 29+) as the primary metric, fall back to totalTimeInForeground.
85+
val rawTime: Long = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
86+
us.totalTimeVisible
87+
} else {
88+
us.totalTimeInForeground
89+
}
90+
91+
if (rawTime > 0) {
92+
val usageStats: WritableMap = WritableNativeMap()
93+
usageStats.putString("packageName", us.packageName)
94+
val totalTimeInSeconds = rawTime / 1000.0
95+
usageStats.putDouble("totalTimeInForeground", totalTimeInSeconds)
96+
usageStats.putDouble("totalTimeVisible", rawTime.toDouble())
97+
usageStats.putDouble("firstTimeStamp", us.firstTimeStamp.toDouble())
98+
usageStats.putDouble("lastTimeStamp", us.lastTimeStamp.toDouble())
99+
usageStats.putDouble("lastTimeUsed", us.lastTimeUsed.toDouble())
100+
usageStats.putBoolean("isSystem", UsageUtils.isSystemApp(packageManager, us.packageName))
101+
usageStats.putString(
102+
"appName",
103+
UsageUtils.parsePackageName(packageManager, us.packageName.toString()).toString()
104+
)
105+
result.pushMap(usageStats)
106+
}
107+
}
108+
promise.resolve(result)
109+
}
110+
111+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1)
112+
fun queryAndAggregateUsageStats(startTime: Double, endTime: Double, promise: Promise) {
113+
val packageManager: PackageManager = reactContext.packageManager
114+
val result: WritableNativeArray = WritableNativeArray()
115+
val usageStatsManager =
116+
reactContext.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
117+
val queryUsageStats: MutableMap<String, UsageStats>? =
118+
usageStatsManager.queryAndAggregateUsageStats(startTime.toLong(), endTime.toLong())
119+
120+
if (queryUsageStats != null) {
121+
for (us in queryUsageStats.values) {
122+
// On API 29+ totalTimeInForeground is always 0 due to OS restrictions.
123+
val rawTime: Long = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
124+
us.totalTimeVisible
125+
} else {
126+
us.totalTimeInForeground
127+
}
128+
129+
if (rawTime > 0) {
130+
val usageStats: WritableMap = WritableNativeMap()
131+
usageStats.putString("packageName", us.packageName)
132+
val totalTimeInSeconds = rawTime.toDouble() / 1000
133+
usageStats.putDouble("totalTimeInForeground", totalTimeInSeconds)
134+
usageStats.putDouble("totalTimeVisible", rawTime.toDouble())
135+
usageStats.putDouble("firstTimeStamp", us.firstTimeStamp.toDouble())
136+
usageStats.putDouble("lastTimeStamp", us.lastTimeStamp.toDouble())
137+
usageStats.putDouble("lastTimeUsed", us.lastTimeUsed.toDouble())
138+
usageStats.putInt("describeContents", us.describeContents())
139+
usageStats.putBoolean("isSystem", UsageUtils.isSystemApp(packageManager, us.packageName.toString()))
140+
usageStats.putString(
141+
"appName",
142+
UsageUtils.parsePackageName(packageManager, us.packageName.toString()).toString()
143+
)
144+
result.pushMap(usageStats)
145+
}
146+
}
147+
}
148+
promise.resolve(result)
149+
}
150+
151+
fun writeToWritableMap(mutableList: MutableList<AppData>): WritableMap {
152+
val writableMap: WritableMap = WritableNativeMap()
153+
for ((index, appData) in mutableList.withIndex()) {
154+
val appDataMap: WritableMap = WritableNativeMap()
155+
appDataMap.putString("name", appData.mName)
156+
appDataMap.putString("packageName", appData.mPackageName)
157+
appDataMap.putDouble("eventTime", appData.mEventTime.toDouble())
158+
appDataMap.putDouble("usageTime", appData.mUsageTime.toDouble())
159+
appDataMap.putString("humanReadableUsageTime", humanReadableMillis(appData.mUsageTime))
160+
appDataMap.putInt("eventType", appData.mEventType)
161+
appDataMap.putInt("count", appData.mCount)
162+
appDataMap.putBoolean("isSystem", appData.mIsSystem)
163+
writableMap.putMap(index.toString(), appDataMap)
164+
}
165+
return writableMap
166+
}
167+
168+
fun queryEvents(startTime: Double, endTime: Double, promise: Promise) {
169+
val result = WritableNativeArray()
170+
val manager =
171+
reactContext.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
172+
val events: UsageEvents = manager.queryEvents(startTime.toLong(), endTime.toLong())
173+
val event = UsageEvents.Event()
174+
while (events.hasNextEvent()) {
175+
events.getNextEvent(event)
176+
val _event: WritableMap = WritableNativeMap()
177+
_event.putInt("eventType", event.eventType)
178+
_event.putDouble("timeStamp", event.timeStamp.toDouble())
179+
_event.putString("packageName", event.packageName)
180+
result.pushMap(_event)
181+
}
182+
promise.resolve(result)
183+
}
184+
185+
// interval is Double to match codegen spec
186+
@RequiresApi(Build.VERSION_CODES.P)
187+
fun queryEventsStats(interval: Double, startTime: Double, endTime: Double, promise: Promise) {
188+
val result: WritableMap = WritableNativeMap()
189+
val usageStatsManager =
190+
reactContext.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
191+
val queryUsageStats: MutableList<EventStats>? =
192+
usageStatsManager.queryEventStats(interval.toInt(), startTime.toLong(), endTime.toLong())
193+
if (queryUsageStats != null) {
194+
for (us in queryUsageStats) {
195+
val usageStats: WritableMap = WritableNativeMap()
196+
usageStats.putDouble("firstTimeStamp", us.firstTimeStamp.toDouble())
197+
usageStats.putDouble("lastTimeStamp", us.lastTimeStamp.toDouble())
198+
usageStats.putDouble("lastTimeUsed", us.totalTime.toDouble())
199+
usageStats.putInt("describeContents", us.describeContents())
200+
result.putMap(us.eventType.toString(), usageStats)
201+
}
202+
}
203+
promise.resolve(result)
204+
}
205+
206+
fun checkForPermission(promise: Promise) {
207+
val appOps =
208+
reactContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
209+
val mode: Int =
210+
appOps.checkOpNoThrow(
211+
OPSTR_GET_USAGE_STATS,
212+
Process.myUid(),
213+
reactContext.packageName
214+
)
215+
promise.resolve(mode == MODE_ALLOWED)
216+
}
217+
218+
@TargetApi(Build.VERSION_CODES.M)
219+
private fun getDataUsage(
220+
networkType: Int,
221+
subscriberId: String?,
222+
packageUid: Int,
223+
startTime: Long,
224+
endTime: Long
225+
): Double {
226+
var currentDataUsage = 0.0
227+
try {
228+
val networkStatsByApp =
229+
networkStatsManager?.querySummary(networkType, subscriberId, startTime, endTime)!!
230+
do {
231+
val bucket = NetworkStats.Bucket()
232+
networkStatsByApp.getNextBucket(bucket)
233+
if (bucket.uid == packageUid) {
234+
currentDataUsage += bucket.rxBytes + bucket.txBytes
235+
}
236+
} while (networkStatsByApp.hasNextBucket())
237+
} catch (e: RemoteException) {
238+
e.printStackTrace()
239+
}
240+
return currentDataUsage
241+
}
242+
243+
// networkType is Double to match codegen spec
244+
fun getAppDataUsage(
245+
packageName: String,
246+
networkType: Double,
247+
startTime: Double,
248+
endTime: Double,
249+
promise: Promise
250+
) {
251+
val uid = getAppUid(packageName)
252+
val netType = networkType.toInt()
253+
when {
254+
netType == ConnectivityManager.TYPE_MOBILE -> promise.resolve(
255+
getDataUsage(ConnectivityManager.TYPE_MOBILE, null, uid, startTime.toLong(), endTime.toLong())
256+
)
257+
netType == ConnectivityManager.TYPE_WIFI -> promise.resolve(
258+
getDataUsage(ConnectivityManager.TYPE_WIFI, "", uid, startTime.toLong(), endTime.toLong())
259+
)
260+
else -> promise.resolve(
261+
getDataUsage(ConnectivityManager.TYPE_MOBILE, "", uid, startTime.toLong(), endTime.toLong()) +
262+
getDataUsage(ConnectivityManager.TYPE_WIFI, "", uid, startTime.toLong(), endTime.toLong())
263+
)
264+
}
265+
}
266+
267+
private fun getAppUid(packageName: String): Int {
268+
return try {
269+
reactContext.packageManager.getApplicationInfo(packageName, 0).uid
270+
} catch (e: PackageManager.NameNotFoundException) {
271+
0
272+
}
273+
}
274+
275+
companion object {
276+
const val MODULE_NAME = "JustDiceReactNativeUsageStats"
277+
}
278+
}
279+
280+
internal class ClonedEvent(event: UsageEvents.Event) {
281+
var packageName: String
282+
var eventClass: String
283+
var timeStamp: Long
284+
var eventType: Int
285+
286+
init {
287+
packageName = event.packageName
288+
eventClass = event.className
289+
timeStamp = event.timeStamp
290+
eventType = event.eventType
291+
}
292+
}
293+
294+
295+
Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,35 @@
11
package io.justdice.usagestats
22

3-
import com.facebook.react.ReactPackage
3+
import com.facebook.react.TurboReactPackage
44
import com.facebook.react.bridge.NativeModule
55
import com.facebook.react.bridge.ReactApplicationContext
6-
import com.facebook.react.uimanager.ViewManager
6+
import com.facebook.react.module.model.ReactModuleInfo
7+
import com.facebook.react.module.model.ReactModuleInfoProvider
78

9+
class UsageStatsManagerPackage : TurboReactPackage() {
810

9-
class UsageStatsManagerPackage : ReactPackage {
10-
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11-
return listOf(UsageStatsManagerModule(reactContext))
11+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12+
return if (name == UsageStatsManagerModuleImpl.MODULE_NAME) {
13+
UsageStatsManagerModule(reactContext)
14+
} else {
15+
null
16+
}
1217
}
1318

14-
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15-
return emptyList()
19+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20+
return ReactModuleInfoProvider {
21+
mapOf(
22+
UsageStatsManagerModuleImpl.MODULE_NAME to
23+
ReactModuleInfo(
24+
UsageStatsManagerModuleImpl.MODULE_NAME,
25+
UsageStatsManagerModuleImpl.MODULE_NAME,
26+
false, // canOverrideExistingModule
27+
false, // needsEagerInit
28+
true, // hasConstants
29+
false, // isCxxModule
30+
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED // isTurboModule
31+
)
32+
)
33+
}
1634
}
1735
}

0 commit comments

Comments
 (0)