Android のバックグラウンドGPSを組み込み
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.rogapp">
|
||||
package="com.dvox.gifunavi">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.rogapp">
|
||||
package="com.dvox.gifunavi">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
@ -42,7 +42,7 @@
|
||||
<meta-data android:name="com.google.android.geo.API_KEY"
|
||||
android:value="AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE"/>
|
||||
<service
|
||||
android:name="com.example.rogapp.LocationService"
|
||||
android:name="com.dvox.gifunavi.LocationService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="location" />
|
||||
|
||||
241
android/app/src/main/kotlin/com/dvox/gifunavi/LocationService.kt
Normal file
241
android/app/src/main/kotlin/com/dvox/gifunavi/LocationService.kt
Normal file
@ -0,0 +1,241 @@
|
||||
package com.dvox.gifunavi // この部分を変更
|
||||
|
||||
import android.location.Location
|
||||
import android.Manifest
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationCallback
|
||||
import com.google.android.gms.location.LocationRequest
|
||||
import com.google.android.gms.location.LocationResult
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import android.app.Notification
|
||||
|
||||
data class GpsData(
|
||||
val id: Int,
|
||||
val team_name: String,
|
||||
val event_code: String,
|
||||
val lat: Double,
|
||||
val lon: Double,
|
||||
val is_checkin: Int,
|
||||
val created_at: Long
|
||||
)
|
||||
|
||||
class GpsDatabaseHelper(private val context: Context) {
|
||||
fun insertGps(gpsData: GpsData) {
|
||||
Log.d("LocationService", "Android: insertGps.")
|
||||
|
||||
// ここにデータベースへの挿入処理を実装する
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(context: Context): GpsDatabaseHelper {
|
||||
Log.d("LocationService", "Android: GpsDatabaseHelper.")
|
||||
return GpsDatabaseHelper(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LocationService : Service() {
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d("LocationService", "Android: onCreate.")
|
||||
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
|
||||
// GPSデバイスが有効になっているか確認する
|
||||
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
Log.d("LocationService", "GPS is disabled.")
|
||||
// GPSが無効の場合の処理を追加する(例: ユーザーにGPSを有効にするように促すなど)
|
||||
}else{
|
||||
Log.d("LocationService", "GPS is enabled.")
|
||||
}
|
||||
|
||||
// フォアグラウンドサービスの設定
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val CHANNEL_ID = "location"
|
||||
val channel = NotificationChannel(CHANNEL_ID, "Location", NotificationManager.IMPORTANCE_DEFAULT)
|
||||
val notificationManager = getSystemService(NotificationManager::class.java) // この行を追加
|
||||
|
||||
Log.d("LocationService", "Android: Foreground service.")
|
||||
|
||||
notificationManager?.createNotificationChannel(channel)
|
||||
}
|
||||
val notification = NotificationCompat.Builder(this, "location")
|
||||
.setContentTitle("Tracking location...")
|
||||
.setContentText("Location: null")
|
||||
.setSmallIcon(android.R.drawable.ic_menu_mylocation) // リソースが存在しないため0を設定
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
Log.d("LocationService", "Android: Foreground service 2.")
|
||||
|
||||
startForeground(1, notification)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.d("LocationService", "Android: onDestroy.")
|
||||
fusedLocationClient.removeLocationUpdates(locationCallback)
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NOTIFICATION_ID = 1
|
||||
private const val CHANNEL_ID = "location_service_channel"
|
||||
}
|
||||
|
||||
private fun createNotification(): Notification {
|
||||
Log.d("LocationService", "Android: createNotification Notification.")
|
||||
val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("Location Service")
|
||||
.setContentText("Running...")
|
||||
.setSmallIcon(android.R.drawable.ic_menu_mylocation)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channelName = "Location Service Channel"
|
||||
val channelDescription = "Channel for Location Service"
|
||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
val channel = NotificationChannel(CHANNEL_ID, channelName, importance).apply {
|
||||
description = channelDescription
|
||||
}
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
return notificationBuilder.build()
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
Log.d("LocationService", "Android: createNotificationChannel.")
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"Location Service Channel",
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
private var lastLocation: Location? = null
|
||||
|
||||
private val locationCallback = object : LocationCallback() {
|
||||
override fun onLocationResult(locationResult: LocationResult) {
|
||||
val currentLocation = locationResult.lastLocation
|
||||
//Log.d("LocationService", "Android: onLocationResult.")
|
||||
|
||||
if (currentLocation != null) {
|
||||
val accuracy = currentLocation.accuracy
|
||||
if (accuracy <= 30) {
|
||||
var lastLocation = lastLocation
|
||||
if (lastLocation == null || currentLocation.distanceTo(lastLocation) >= 10) {
|
||||
val lat = currentLocation.latitude
|
||||
val lon = currentLocation.longitude
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
// GPS データをデバッグ用に表示
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
||||
val formattedTime = sdf.format(Date(currentTime))
|
||||
//Log.d("LocationService", "Latitude: $lat, Longitude: $lon, Time: $formattedTime, Accuracy: $accuracy")
|
||||
|
||||
// GPS データをデータベースに保存
|
||||
GlobalScope.launch {
|
||||
addGPStoDB(lat, lon, currentTime)
|
||||
}
|
||||
|
||||
lastLocation = currentLocation
|
||||
}
|
||||
} else {
|
||||
Log.d("LocationService", "Android: GPS accuracy is above 30m. Skipping data saving.")
|
||||
}
|
||||
} else {
|
||||
Log.d("LocationService", "Android: No GPS signal received.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.d("LocationService", "Android: onStartCommand.")
|
||||
|
||||
// 位置情報の権限チェックとGPS有効化の確認を行う
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
val locationRequest = LocationRequest.create().apply {
|
||||
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
|
||||
interval = 10000
|
||||
fastestInterval = 5000
|
||||
}
|
||||
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
|
||||
} else {
|
||||
Log.d("LocationService", "GPS is disabled.")
|
||||
// GPSが無効の場合の処理を追加する(例: ユーザーにGPSを有効にするように促すなど)
|
||||
}
|
||||
} else {
|
||||
Log.d("LocationService", "Location permission is not granted.")
|
||||
// 位置情報の権限が許可されていない場合の処理を追加する
|
||||
}
|
||||
|
||||
// Foregroundサービスを開始
|
||||
startForeground(NOTIFICATION_ID, createNotification())
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private suspend fun addGPStoDB(lat: Double, lng: Double, timestamp: Long, isCheckin: Int = 0) {
|
||||
try {
|
||||
val context = applicationContext
|
||||
val preferences = context.getSharedPreferences("RogPreferences", Context.MODE_PRIVATE)
|
||||
val teamName = preferences.getString("team_name", "") ?: ""
|
||||
val eventCode = preferences.getString("event_code", "") ?: ""
|
||||
|
||||
|
||||
if (teamName.isNotEmpty() && eventCode.isNotEmpty()) {
|
||||
val gpsData = GpsData(
|
||||
id = 0,
|
||||
team_name = teamName,
|
||||
event_code = eventCode,
|
||||
lat = lat,
|
||||
lon = lng,
|
||||
is_checkin = isCheckin,
|
||||
created_at = timestamp
|
||||
)
|
||||
|
||||
val db = GpsDatabaseHelper.getInstance(context)
|
||||
db.insertGps(gpsData)
|
||||
Log.d("LocationService", "Android: addGPStoDB.")
|
||||
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("LocationService", "Error adding GPS data to DB", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
114
android/app/src/main/kotlin/com/dvox/gifunavi/MainActivity.kt
Normal file
114
android/app/src/main/kotlin/com/dvox/gifunavi/MainActivity.kt
Normal file
@ -0,0 +1,114 @@
|
||||
package com.dvox.gifunavi // この部分を変更
|
||||
|
||||
import android.Manifest
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import android.util.Log
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private val CHANNEL = "location"
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
|
||||
call, result ->
|
||||
when (call.method) {
|
||||
"startLocationService" -> {
|
||||
Log.d("MainActivity", "Android: called startLocationService.")
|
||||
//val intent = Intent(this, LocationService::class.java)
|
||||
startLocationService()
|
||||
result.success(null)
|
||||
}
|
||||
"stopLocationService" -> {
|
||||
Log.d("MainActivity", "Android: called stopLocationService.")
|
||||
//val intent = Intent(this, LocationService::class.java)
|
||||
stopLocationService()
|
||||
result.success(null)
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d("MainActivity", "Android: onCreate.")
|
||||
|
||||
// 位置情報の権限をリクエストする
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_REQUEST_CODE)
|
||||
} else {
|
||||
// startLocationService() // アプリ起動時にLocationServiceを開始する ==> main.dartで制御する。
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// startLocationService()
|
||||
|
||||
Log.d("MainActivity", "Android: PERMISSION_GRANTED.")
|
||||
} else {
|
||||
// 位置情報の権限が拒否された場合の処理
|
||||
Toast.makeText(this, "Location permission denied.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PERMISSION_REQUEST_CODE = 1
|
||||
}
|
||||
|
||||
private fun startLocationService() {
|
||||
Log.d("MainActivity", "Android: startLocationService.")
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (!isServiceRunning(LocationService::class.java)) {
|
||||
val intent = Intent(this, LocationService::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Log.d("MainActivity", "startForegroundService")
|
||||
startForegroundService(intent)
|
||||
} else {
|
||||
Log.d("MainActivity", "startService")
|
||||
startService(intent)
|
||||
}
|
||||
} else {
|
||||
Log.d("MainActivity", "Location service is already running.")
|
||||
}
|
||||
} else {
|
||||
Log.d("MainActivity", "Location permission is not granted.")
|
||||
// 位置情報の権限が許可されていない場合の処理を追加する
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopLocationService() {
|
||||
Log.d("MainActivity", "Android: stopLocationService.")
|
||||
val intent = Intent(this, LocationService::class.java)
|
||||
stopService(intent)
|
||||
}
|
||||
|
||||
private fun isServiceRunning(serviceClass: Class<*>): Boolean {
|
||||
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
for (service in manager.getRunningServices(Int.MAX_VALUE)) {
|
||||
if (serviceClass.name == service.service.className) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
||||
class LocationService : Service() {
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// フォアグラウンドサービスの設定
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
"location",
|
||||
"Location",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
val notification = NotificationCompat.Builder(this, "location")
|
||||
.setContentTitle("Tracking location...")
|
||||
.setContentText("Location: null")
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
startForeground(1, notification)
|
||||
}
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
// バックグラウンドでの位置情報取得処理を実装
|
||||
val locationSettings = LocationSettings.Builder()
|
||||
.setAccuracy(LocationAccuracy.HIGH)
|
||||
.setDistanceFilter(100.0f)
|
||||
.build()
|
||||
|
||||
try {
|
||||
Geolocator.getPositionStream(locationSettings)
|
||||
.catch { e ->
|
||||
// エラーハンドリング
|
||||
println("Location Error: $e")
|
||||
null
|
||||
}
|
||||
.filterNotNull()
|
||||
.filter { position ->
|
||||
// GPS信号がlow以上の場合のみ記録
|
||||
position.accuracy <= 100
|
||||
}
|
||||
.onEach { position ->
|
||||
val lat = position.latitude
|
||||
val lng = position.longitude
|
||||
val timestamp = System.currentTimeMillis()
|
||||
|
||||
// データベースに位置情報を保存
|
||||
addGPStoDB(lat, lng, timestamp)
|
||||
}
|
||||
.launchIn(CoroutineScope(Dispatchers.IO))
|
||||
} catch (e: Exception) {
|
||||
println("Error starting background tracking: $e")
|
||||
}
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun addGPStoDB(lat: Double, lng: Double, isCheckin: Int = 0) {
|
||||
try {
|
||||
val context = applicationContext
|
||||
val preferences = context.getSharedPreferences("RogPreferences", Context.MODE_PRIVATE)
|
||||
val teamName = preferences.getString("team_name", "") ?: ""
|
||||
val eventCode = preferences.getString("event_code", "") ?: ""
|
||||
|
||||
if (teamName.isNotEmpty() && eventCode.isNotEmpty()) {
|
||||
val gpsData = GpsData(
|
||||
id = 0,
|
||||
team_name = teamName,
|
||||
event_code = eventCode,
|
||||
lat = lat,
|
||||
lon = lng,
|
||||
is_checkin = isCheckin,
|
||||
created_at = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
GlobalScope.launch {
|
||||
val db = GpsDatabaseHelper.getInstance(context)
|
||||
db.insertGps(gpsData)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Error adding GPS data to DB: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package com.example.rogapp
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
import androidx.annotation.NonNull
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private val CHANNEL = "location"
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
|
||||
call, result ->
|
||||
when (call.method) {
|
||||
"startLocationService" -> {
|
||||
val intent = Intent(this, LocationService::class.java)
|
||||
startService(intent)
|
||||
result.success(null)
|
||||
}
|
||||
"stopLocationService" -> {
|
||||
val intent = Intent(this, LocationService::class.java)
|
||||
stopService(intent)
|
||||
result.success(null)
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user