Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionality to check and make app restricted for powerSavingMode #137

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />

<application
android:name=".YourSpaceApplication"
Expand Down Expand Up @@ -59,6 +60,15 @@
</intent-filter>
</receiver>

<receiver
android:name=".domain.receiver.PowerSavingModeObserver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_SAVE_MODE_CHANGED" />
</intent-filter>
</receiver>

<activity
android:name="com.canhub.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.canopas.yourspace.domain.fcm.YOURSPACE_CHANNEL_GEOFENCE
import com.canopas.yourspace.domain.fcm.YOURSPACE_CHANNEL_MESSAGES
import com.canopas.yourspace.domain.fcm.YOURSPACE_CHANNEL_PLACES
import com.canopas.yourspace.domain.receiver.BatteryBroadcastReceiver
import com.canopas.yourspace.domain.receiver.PowerSavingModeObserver
import com.google.android.libraries.places.api.Places
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.hilt.android.HiltAndroidApp
Expand Down Expand Up @@ -48,14 +49,18 @@ class YourSpaceApplication :
@Inject
lateinit var notificationManager: NotificationManager

@Inject
lateinit var powerSavingModeObserver: PowerSavingModeObserver

override fun onCreate() {
Places.initializeWithNewPlacesApiEnabled(this, BuildConfig.PLACE_API_KEY)

super<Application>.onCreate()
Timber.plant(Timber.DebugTree())
FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = !BuildConfig.DEBUG
FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = false
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
authService.addListener(this)
powerSavingModeObserver.initReceiver(this)
setNotificationChannel()

registerBatteryBroadcastReceiver()
Expand All @@ -68,6 +73,7 @@ class YourSpaceApplication :
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
unregisterReceiver(BatteryBroadcastReceiver(authService))
powerSavingModeObserver.unregisterReceiver()
}

private fun registerBatteryBroadcastReceiver() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.canopas.yourspace.domain.receiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.PowerManager
import com.canopas.yourspace.data.service.auth.AuthService
import com.canopas.yourspace.domain.utils.cancelPowerSavingNotification
import com.canopas.yourspace.domain.utils.sendPowerSavingNotification
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

@AndroidEntryPoint
class PowerSavingModeObserver @Inject constructor(
private val authService: AuthService
) : BroadcastReceiver() {

private var context: Context? = null

fun initReceiver(context: Context) {
this.context = context
val filter = IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
context.registerReceiver(this, filter)
}

override fun onReceive(context: Context?, intent: Intent?) {
val powerManager = context?.getSystemService(Context.POWER_SERVICE) as PowerManager
val isPowerSaving = powerManager.isPowerSaveMode

if (isPowerSaving) {
sendPowerSavingNotification(context)
} else {
cancelPowerSavingNotification(context)
}

if (intent?.action == PowerManager.ACTION_POWER_SAVE_MODE_CHANGED) {
CoroutineScope(Dispatchers.IO).launch {
try {
authService.updatePowerSaveModeStatus(isPowerSaving)
} catch (e: Exception) {
Timber.e(e, "Failed to update battery status")
}
}
}
}

fun unregisterReceiver() {
context?.unregisterReceiver(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.canopas.yourspace.domain.utils

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import com.canopas.yourspace.R
import com.canopas.yourspace.ui.MainActivity
import timber.log.Timber

const val YOURSPACE_CHANNEL_POWER_SAVING = "your_space_notification_channel_power_saving"
const val NOTIFICATION_ID_POWER_SAVING = 102

fun sendPowerSavingNotification(context: Context) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

val notificationBuilder = NotificationCompat.Builder(context, YOURSPACE_CHANNEL_POWER_SAVING)
.setSmallIcon(R.drawable.app_logo)
.setContentTitle(context.getString(R.string.battery_saving_notification_title))
.setContentText(context.getString(R.string.battery_saving_notification_description))
.setAutoCancel(true)
.setContentIntent(pendingIntent)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
YOURSPACE_CHANNEL_POWER_SAVING,
context.getString(R.string.notification_channel_power_saving),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}

Timber.e("Power saving notification sent")
notificationManager.notify(NOTIFICATION_ID_POWER_SAVING, notificationBuilder.build())
}

fun cancelPowerSavingNotification(context: Context) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(NOTIFICATION_ID_POWER_SAVING)
Timber.e("Power saving notification canceled")
}
57 changes: 57 additions & 0 deletions app/src/main/java/com/canopas/yourspace/ui/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.canopas.yourspace.ui

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import android.view.Window
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
Expand All @@ -16,6 +21,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.navigation.compose.hiltViewModel
Expand Down Expand Up @@ -61,28 +67,43 @@ import com.canopas.yourspace.ui.navigation.slideComposable
import com.canopas.yourspace.ui.theme.CatchMeTheme
import com.google.gson.Gson
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

private val viewModel: MainViewModel by viewModels()

private val powerSaveReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val isPowerSavingMode = viewModel.isPowerSavingModeEnabled(context ?: return)
viewModel.updatePowerSavingState(isPowerSavingMode)
}
}
cp-sneh-s marked this conversation as resolved.
Show resolved Hide resolved

override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE)

super.onCreate(savedInstanceState)

val filter = IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
registerReceiver(powerSaveReceiver, filter)

setContent {
CatchMeTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val context = LocalContext.current

MainApp(viewModel)

LaunchedEffect(Unit) {
viewModel.handleIntentData(intent)
val isPowerSavingEnable = viewModel.isPowerSavingModeEnabled(context)
viewModel.updatePowerSavingState(isPowerSavingEnable) // Ensure initial state is set
}
}
}
Expand All @@ -95,6 +116,11 @@ class MainActivity : ComponentActivity() {
viewModel.handleIntentData(intent)
intent.extras?.clear()
}

override fun onDestroy() {
super.onDestroy()
unregisterReceiver(powerSaveReceiver)
}
}

@OptIn(ExperimentalAnimationApi::class)
Expand All @@ -103,6 +129,10 @@ fun MainApp(viewModel: MainViewModel) {
val navController = rememberNavController()
val state by viewModel.state.collectAsState()

if (state.isPowerSavingEnabled) {
PowerSavingAlertPopup()
}

if (state.isSessionExpired) {
SessionExpiredAlertPopup()
}
Expand Down Expand Up @@ -287,3 +317,30 @@ fun SpaceNotFoundPopup() {
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
)
}

@Composable
fun PowerSavingAlertPopup() {
val viewModel = hiltViewModel<MainViewModel>()
val context = LocalContext.current

Timber.d("XXX :- PowerSavingAlertPopup: Displaying power saving dialog")

AppAlertDialog(
title = stringResource(R.string.battery_saver_dialog_title),
subTitle = stringResource(R.string.battery_saver_dialog_description),
confirmBtnText = stringResource(R.string.btn_turn_off),
dismissBtnText = stringResource(R.string.common_btn_cancel),
onConfirmClick = {
try {
val intent = Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
context.startActivity(intent)
} catch (e: Exception) {
Timber.e("PowerSavingAlertPopup", "Failed to open battery saver settings", e)
}
},
onDismissClick = {
viewModel.dismissPowerSavingDialog()
},
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
)
}
18 changes: 17 additions & 1 deletion app/src/main/java/com/canopas/yourspace/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.canopas.yourspace.ui

import android.content.Context
import android.content.Intent
import android.os.PowerManager
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.canopas.yourspace.data.models.user.ApiUserSession
Expand Down Expand Up @@ -143,12 +145,26 @@ class MainViewModel @Inject constructor(
fun dismissSpaceNotFoundPopup() {
_state.value = state.value.copy(showSpaceNotFoundPopup = false)
}

fun updatePowerSavingState(isEnabled: Boolean) {
_state.value = state.value.copy(isPowerSavingEnabled = isEnabled)
}

fun dismissPowerSavingDialog() {
_state.value = state.value.copy(isPowerSavingEnabled = false)
}

fun isPowerSavingModeEnabled(context: Context): Boolean {
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
return powerManager.isPowerSaveMode
}
}

data class MainScreenState(
val isSessionExpired: Boolean = false,
val initialRoute: String? = null,
val verifyingSpace: Boolean = false,
val showSpaceNotFoundPopup: Boolean = false,
val isInitialRouteSet: Boolean = false
val isInitialRouteSet: Boolean = false,
val isPowerSavingEnabled: Boolean = false
)
Loading
Loading