Skip to content

Commit

Permalink
Add saving call logs to text file in download folder
Browse files Browse the repository at this point in the history
  • Loading branch information
itsmadhusudhan committed Dec 29, 2023
0 parents commit c0da7db
Show file tree
Hide file tree
Showing 40 changed files with 1,379 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
.idea/
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
70 changes: 70 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}

android {
namespace = "com.shreekaram.calllogger"
compileSdk = 34

defaultConfig {
applicationId = "com.shreekaram.calllogger"
minSdk = 28
targetSdk = 34
versionCode = 2
versionName = "1.1"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}

dependencies {

implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.0")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.work:work-runtime-ktx:2.9.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.shreekaram.calllogger

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.shreekaram.calllogger", appContext.packageName)
}
}
57 changes: 57 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CallLogger"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.CallLogger">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>



<receiver
android:name=".CallStateReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.READ_PHONE_STATE">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
</application>


</manifest>
141 changes: 141 additions & 0 deletions app/src/main/java/com/shreekaram/calllogger/CallLogWorker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.shreekaram.calllogger

import android.Manifest
import android.app.Notification
import android.app.NotificationManager
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.provider.CallLog
import android.provider.MediaStore
import android.text.format.DateUtils
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import java.io.File
import java.io.OutputStream
import kotlin.streams.asSequence

class CallLogWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// empty list if Call record
val callRecords = mutableListOf<CallRecord>()
// val dateFrom = "1703751885344"
// access call logs
val cursor = applicationContext.contentResolver.query(
CallLog.Calls.CONTENT_URI,
null,
null,
null,
CallLog.Calls.DATE + " DESC"
)
// val cursor = contentResolver.query(
// CallLog.Calls.CONTENT_URI,
// null,
// "${CallLog.Calls.DATE} >= ?",
// arrayOf(dateFrom),
// CallLog.Calls.DATE + " DESC"
// )

cursor?.use {
val number = it.getColumnIndex(CallLog.Calls.NUMBER)
val type = it.getColumnIndex(CallLog.Calls.TYPE)
val date = it.getColumnIndex(CallLog.Calls.DATE)
val duration = it.getColumnIndex(CallLog.Calls.DURATION)
val name = it.getColumnIndex(CallLog.Calls.CACHED_NAME)


while (it.moveToNext()) {
val phoneNumber = it.getString(number)
val callType = it.getString(type)

var callDate = it.getString(date)
//convert date to human readable format
// callDate = DateUtils.formatDateTime(
// applicationContext,
// callDate.toLong(),
// DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_SHOW_TIME
// )

val callDuration = it.getString(duration)
val callerName = it.getString(name)?:"Unknown"
callRecords.add(
CallRecord(
callerName,
phoneNumber,
callType,
callDate,
callDuration
)
)
}

}
cursor?.close()
println(callRecords)

// store the call records into file
// human readable date format with file name
val time= DateUtils.formatDateTime(
applicationContext,
System.currentTimeMillis(),
DateUtils.FORMAT_SHOW_TIME
)


val fileName = "callRecords_$time.txt"
println("Printing file name $fileName")
val content=callRecords.toString()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver = applicationContext.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}

val uri = resolver.insert(MediaStore.Files.getContentUri("external"), contentValues)
uri?.let {
val outputStream: OutputStream? = resolver.openOutputStream(it)
outputStream?.bufferedWriter()?.use { it.write(content) }
// close stream
outputStream?.close()
}


} else {
val downloadsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(downloadsDirectory, fileName)
file.writeText(content)
}

val notification= NotificationCompat.Builder(applicationContext,"call_logger")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Call Log file $fileName")
.setContentText("Saved to Downloads. Good day to you!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()

with(NotificationManagerCompat.from(applicationContext)){
if (ActivityCompat.checkSelfPermission(
applicationContext,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return@with
}

// generate random int for notification id
val notificationId=(0..100).random()

notify(notificationId,notification)
}

return Result.success()
}
}
33 changes: 33 additions & 0 deletions app/src/main/java/com/shreekaram/calllogger/CallStateReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.shreekaram.calllogger

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.TelephonyManager
import android.util.Log

class CallStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == TelephonyManager.ACTION_PHONE_STATE_CHANGED) {
val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
val incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_VOICEMAIL_NUMBER)
Log.d("CallStateReceiver", state.toString())
Log.d("CallStateReceiver", incomingNumber.toString())
when (state) {
TelephonyManager.EXTRA_STATE_RINGING -> {
// The phone is ringing
Log.d("CallStateReceiver", "Incoming call from $incomingNumber")
}
TelephonyManager.EXTRA_STATE_OFFHOOK -> {
// A call is dialing, active or on hold
Log.d("CallStateReceiver", "Call answered")
}
TelephonyManager.EXTRA_STATE_IDLE -> {
// The phone is idle
Log.d("CallStateReceiver", "Call ended")

}
}
}
}
}
Loading

0 comments on commit c0da7db

Please sign in to comment.