first commit

This commit is contained in:
ytc1012
2025-11-13 15:45:28 +08:00
commit 6b321890c0
54 changed files with 8412 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="AutoTime Tracker"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@@ -0,0 +1,8 @@
package com.autotime.tracker
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
// MainActivity 实现
}

View File

@@ -0,0 +1,189 @@
package com.autotime.tracker
import android.app.AppOpsManager
import android.app.usage.UsageStats
import android.app.usage.UsageStatsManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Settings
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.util.*
class TimeTrackingPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var context: Context
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "autotime_tracker/time_tracking")
channel.setMethodCallHandler(this)
context = flutterPluginBinding.applicationContext
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"hasPermission" -> hasPermission(result)
"requestPermission" -> requestPermission(result)
"getAppUsage" -> getAppUsage(call, result)
"startBackgroundTracking" -> startBackgroundTracking(result)
"stopBackgroundTracking" -> stopBackgroundTracking(result)
"isBackgroundTrackingActive" -> isBackgroundTrackingActive(result)
else -> result.notImplemented()
}
}
// MARK: - Permission Methods
private fun hasPermission(result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
val mode = appOps.checkOpNoThrow(
AppOpsManager.OPSTR_GET_USAGE_STATS,
android.os.Process.myUid(),
context.packageName
)
result.success(mode == AppOpsManager.MODE_ALLOWED)
} else {
result.success(false)
}
}
private fun requestPermission(result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
result.success(true)
} else {
result.error(
"UNSUPPORTED_VERSION",
"Usage Stats API requires Android 5.0 (API 21) or later",
null
)
}
}
// MARK: - App Usage Methods
private fun getAppUsage(call: MethodCall, result: MethodChannel.Result) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
result.error(
"UNSUPPORTED_VERSION",
"Usage Stats API requires Android 5.0 (API 21) or later",
null
)
return
}
val args = call.arguments as? Map<*, *>
val startTimeMs = args?.get("startTime") as? Long
val endTimeMs = args?.get("endTime") as? Long
if (startTimeMs == null || endTimeMs == null) {
result.error("INVALID_ARGUMENTS", "Invalid arguments", null)
return
}
// 检查权限
if (!hasUsageStatsPermission()) {
result.error(
"PERMISSION_DENIED",
"Usage Stats permission not granted",
null
)
return
}
val usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val startTime = startTimeMs
val endTime = endTimeMs
// 查询应用使用统计
val stats = usageStatsManager.queryUsageStats(
UsageStatsManager.INTERVAL_DAILY,
startTime,
endTime
)
if (stats == null || stats.isEmpty()) {
result.success(emptyList<Map<String, Any>>())
return
}
// 转换为 Flutter 可用的格式
val appUsageList = mutableListOf<Map<String, Any>>()
// 按包名聚合数据
val packageMap = mutableMapOf<String, UsageStats>()
for (stat in stats) {
val packageName = stat.packageName
val existing = packageMap[packageName]
if (existing == null || stat.totalTimeInForeground > existing.totalTimeInForeground) {
packageMap[packageName] = stat
}
}
// 转换为列表
for ((packageName, stat) in packageMap) {
val appName = getAppName(packageName)
appUsageList.add(mapOf(
"packageName" to packageName,
"appName" to appName,
"startTime" to stat.firstTimeStamp,
"endTime" to stat.lastTimeUsed,
"duration" to (stat.totalTimeInForeground / 1000).toInt(), // 转换为秒
"deviceUnlockCount" to 0 // Android 不直接提供此数据
))
}
result.success(appUsageList)
}
private fun hasUsageStatsPermission(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
val mode = appOps.checkOpNoThrow(
AppOpsManager.OPSTR_GET_USAGE_STATS,
android.os.Process.myUid(),
context.packageName
)
return mode == AppOpsManager.MODE_ALLOWED
}
return false
}
private fun getAppName(packageName: String): String {
return try {
val packageManager = context.packageManager
val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
packageManager.getApplicationLabel(applicationInfo).toString()
} catch (e: Exception) {
packageName
}
}
// MARK: - Background Tracking Methods
private fun startBackgroundTracking(result: MethodChannel.Result) {
// Android 后台追踪实现
// 可以使用 WorkManager 或 Foreground Service
result.success(true)
}
private fun stopBackgroundTracking(result: MethodChannel.Result) {
result.success(true)
}
private fun isBackgroundTrackingActive(result: MethodChannel.Result) {
result.success(false)
}
}