<manifest xmlns:android="http://schemas.android.
com/apk/res/android"
package="com.ankit.dosecare">
<!-- Required Permissions -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<application
android:allowBackup="true"
android:theme="@style/Theme.DoseCare"
android:usesCleartextTraffic="true"
android:label="@string/app_name">
<!-- Main Activity (Launcher) -->
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Other Activities -->
<activity android:name=".AddReminderActivity"/>
<activity android:name=".ScheduleActivity"/>
<!-- Alarm Receiver (Handles Alarms & Reboot) -->
<receiver android:name=".AlarmReceiver"
android:exported="false"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
<!-- Required for AlarmManager -->
<service android:name=".AlarmService"
android:permission="android.permission.FOREGROUND_SERVICE"
android:exported="false"/>
</application>
</manifest>
package com.ankit.dosecare
import android.app.AlarmManager
import android.app.PendingIntent
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.ankit.dosecare.database.Pill
import com.ankit.dosecare.database.PillDatabase
import com.ankit.dosecare.databinding.ActivityAddReminderBinding
import java.util.*
class AddReminderActivity : AppCompatActivity() {
private lateinit var binding: ActivityAddReminderBinding
private lateinit var database: PillDatabase
private var selectedHour = 0
private var selectedMinute = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAddReminderBinding.inflate(layoutInflater)
setContentView(binding.root)
database = PillDatabase.getDatabase(this)
// Time Picker with AM/PM
binding.etPillTime.setOnClickListener {
val calendar = Calendar.getInstance()
val hour = calendar.get(Calendar.HOUR_OF_DAY)
val minute = calendar.get(Calendar.MINUTE)
val timePicker = TimePickerDialog(this, { _, hourOfDay, minute ->
selectedHour = hourOfDay
selectedMinute = minute
val amPm = if (hourOfDay < 12) "AM" else "PM"
val displayHour = if (hourOfDay % 12 == 0) 12 else hourOfDay % 12
binding.etPillTime.setText(String.format("%02d:%02d %s", displayHour, minute, amPm))
}, hour, minute, false)
timePicker.show()
}
binding.btnSaveReminder.setOnClickListener {
val pillName = binding.etPillName.text.toString().trim()
if (pillName.isNotEmpty()) {
val pillTime = String.format("%02d:%02d", selectedHour, selectedMinute)
val pill = Pill(0, pillName, "500mg", pillTime)
Thread {
database.pillDao().insertPill(pill)
runOnUiThread {
setAlarm(selectedHour, selectedMinute)
Toast.makeText(this, "Reminder Added", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}.start()
} else {
Toast.makeText(this, "Enter Pill Name", Toast.LENGTH_SHORT).show()
}
}
// Remove Reminder
binding.btnRemoveReminder.setOnClickListener {
val pillName = binding.etPillName.text.toString().trim()
if (pillName.isNotEmpty()) {
Thread {
val pill = database.pillDao().getPillByName(pillName)
if (pill != null) {
database.pillDao().deletePill(pill)
cancelAlarm()
runOnUiThread {
Toast.makeText(this, "Reminder Removed", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
}
} else {
runOnUiThread {
Toast.makeText(this, "Pill Not Found", Toast.LENGTH_SHORT).show()
}
}
}.start()
} else {
Toast.makeText(this, "Enter Pill Name to Remove", Toast.LENGTH_SHORT).show()
}
}
}
private fun setAlarm(hour: Int, minute: Int) {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT)
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
}
if (calendar.timeInMillis < System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_YEAR, 1) // If time is past, schedule for next day
}
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis,
pendingIntent)
}
private fun cancelAlarm() {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT)
alarmManager.cancel(pendingIntent)
}
}
package com.ankit.dosecare
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.util.Log
import com.ankit.dosecare.database.Pill
import java.util.*
object AlarmHelper {
fun setAlarm(context: Context, pill: Pill) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmReceiver::class.java).apply {
putExtra("PILL_NAME", pill.name)
}
val pendingIntent = PendingIntent.getBroadcast(
context, pill.id, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
try {
val timeParts = pill.time.split(":")
if (timeParts.size != 2) {
Log.e("AlarmHelper", " Invalid time format: ${pill.time}")
return
}
val hour = timeParts[0].toIntOrNull()
val minute = timeParts[1].toIntOrNull()
if (hour == null || minute == null || hour !in 0..23 || minute !in 0..59) {
Log.e("AlarmHelper", " Invalid time values: $hour:$minute")
return
}
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, hour)
set(Calendar.MINUTE, minute)
set(Calendar.SECOND, 0)
// Ensure alarm is in the future
if (before(Calendar.getInstance())) {
add(Calendar.DATE, 1) // Set for tomorrow if time has passed
}
}
Log.d("AlarmHelper", " Alarm set for: ${calendar.time}")
// Best for Doze mode (Allows alarms in idle state)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
// Use `setAlarmClock()` for better accuracy on newer devices
alarmManager.setAlarmClock(
AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingIntent),
pendingIntent
)
} catch (e: Exception) {
Log.e("AlarmHelper", " Error setting alarm: ${e.message}", e)
}
}
}
package com.ankit.dosecare
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.os.PowerManager
import android.widget.Toast
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null) return
Toast.makeText(context, " Alarm Ringing!", Toast.LENGTH_LONG).show()
// Wake up the device if it's sleeping
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"DoseCare:WakeLock"
)
wakeLock.acquire(60 * 1000L) // Keep awake for 1 minute
// Play Alarm Sound
val alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
val mediaPlayer = MediaPlayer.create(context, alarmUri)
mediaPlayer?.start()
wakeLock.release() // Release WakeLock after execution
}
}
package com.ankit.dosecare
import android.Manifest
import android.app.AlarmManager
import android.content.pm.PackageManager
import android.os.Build
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.ankit.dosecare.database.PillDatabase
import com.ankit.dosecare.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var adapter: PillAdapter
private lateinit var database: PillDatabase
private val PERMISSION_REQUEST_CODE = 1001
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
database = PillDatabase.getDatabase(this)
adapter = PillAdapter(database.pillDao().getAllPills()) { pill -> }
binding.rvPillList.layoutManager = LinearLayoutManager(this)
binding.rvPillList.adapter = adapter
binding.fabAdd.setOnClickListener {
startActivity(Intent(this, AddReminderActivity::class.java))
}
binding.fabCalendar.setOnClickListener {
startActivity(Intent(this, ScheduleActivity::class.java))
}
checkAndRequestPermissions()
}
private fun checkAndRequestPermissions() {
val permissions = mutableListOf<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
if (!alarmManager.canScheduleExactAlarms()) {
startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.POST_NOTIFICATIONS)
}
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_BOOT_COMPLETED)
!= PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.RECEIVE_BOOT_COMPLETED)
}
if (permissions.isNotEmpty()) {
ActivityCompat.requestPermissions(this, permissions.toTypedArray(),
PERMISSION_REQUEST_CODE)
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
){
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE) {
for (i in permissions.indices) {
val status = if (grantResults[i] == PackageManager.PERMISSION_GRANTED) "granted" else
"denied"
Toast.makeText(this, "${permissions[i]} $status", Toast.LENGTH_SHORT).show()
}
}
}
}
package com.ankit.dosecare.database
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "pills")
data class Pill(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String,
val dosage: String,
val time: String
)
package com.ankit.dosecare
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ankit.dosecare.database.Pill
class PillActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: PillAdapter
private val pillList = mutableListOf<Pill>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pill)
recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
pillList.add(Pill(1, "Paracetamol", "500mg", "8:00 AM"))
pillList.add(Pill(2, "Ibuprofen", "200mg", "2:00 PM"))
adapter = PillAdapter(pillList) { pill ->
Toast.makeText(this, "Clicked: ${pill}", Toast.LENGTH_SHORT).show()
}
recyclerView.adapter = adapter
}
}
package com.ankit.dosecare
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.ankit.dosecare.database.Pill
class PillAdapter(private val pillList: List<Pill>, private val onClick: (Pill) -> Unit) :
RecyclerView.Adapter<PillAdapter.PillViewHolder>() {
class PillViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val pillName: TextView = itemView.findViewById(R.id.pillName)
val pillDosage: TextView = itemView.findViewById(R.id.pillDosage)
val pillTime: TextView = itemView.findViewById(R.id.pillTime)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PillViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_pill, parent, false)
return PillViewHolder(view)
}
override fun onBindViewHolder(holder: PillViewHolder, position: Int) {
val pill = pillList[position]
holder.pillName.text = pill.name
holder.pillDosage.text = pill.dosage
holder.pillTime.text = pill.time
holder.itemView.setOnClickListener { onClick(pill) }
}
override fun getItemCount() = pillList.size
}
package com.ankit.dosecare.database
import androidx.room.*
@Dao
interface PillDao {
@Query("SELECT * FROM pills")
fun getAllPills(): List<Pill>
@Query("SELECT * FROM pills WHERE name = :pillName LIMIT 1")
fun getPillByName(pillName: String): Pill?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPill(pill: Pill)
@Delete
fun deletePill(pill: Pill)
}
package com.ankit.dosecare.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [Pill::class], version = 1)
abstract class PillDatabase : RoomDatabase() {
abstract fun pillDao(): PillDao
companion object {
@Volatile
private var INSTANCE: PillDatabase? = null
fun getDatabase(context: Context): PillDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
PillDatabase::class.java,
"pill_database"
).allowMainThreadQueries().build()
INSTANCE = instance
instance
}
}
}
}
package com.ankit.dosecare
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.ankit.dosecare.database.PillDatabase
import com.ankit.dosecare.databinding.ActivityScheduleBinding
class ScheduleActivity : AppCompatActivity() {
private lateinit var binding: ActivityScheduleBinding
private lateinit var adapter: PillAdapter
private lateinit var database: PillDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityScheduleBinding.inflate(layoutInflater)
setContentView(binding.root)
database = PillDatabase.getDatabase(this)
adapter = PillAdapter(database.pillDao().getAllPills()) { pill ->
binding.rvScheduleList.layoutManager = LinearLayoutManager(this)
binding.rvScheduleList.adapter = adapter
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp">
<!-- Pill Name Input -->
<EditText
android:id="@+id/etPillName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Pill Name"
android:textSize="16sp"
android:padding="12dp"
android:background="@android:drawable/editbox_background"
android:layout_marginBottom="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Select Time Label -->
<TextView
android:id="@+id/tvTimeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reminder Time"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/etPillName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Selected Time Display -->
<EditText
android:id="@+id/etPillTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="No Time Selected"
android:textSize="16sp"
android:padding="12dp"
android:background="@android:drawable/editbox_background"
android:focusable="false"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/tvTimeLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Pick Time Button -->
<Button
android:id="@+id/btnPickTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pick Time"
android:textSize="16sp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="@id/etPillTime"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Save Reminder Button -->
<Button
android:id="@+id/btnSaveReminder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save Reminder"
android:textSize="16sp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/btnPickTime"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Remove Reminder Button -->
<Button
android:id="@+id/btnRemoveReminder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remove Reminder"
android:textSize="16sp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="@id/btnSaveReminder"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvWelcome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome to DoseCare!"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginTop="32dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvPillList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="16dp"
app:layout_constraintTop_toBottomOf="@id/tvWelcome"
app:layout_constraintBottom_toTopOf="@id/fabAdd"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="16dp"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabCalendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_calendar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_margin="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<CalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvScheduleList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="16dp"
app:layout_constraintTop_toBottomOf="@id/calendarView"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/pillName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pill Name"
android:textSize="18sp" />
<TextView
android:id="@+id/pillDosage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dosage"
android:textSize="16sp" />
<TextView
android:id="@+id/pillTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Time"
android:textSize="16sp" />
</LinearLayout>