The Caller ID App is designed to provide advanced call screening, contact management, and real-time information display with a modern UI and smooth animations. It utilizes the latest Android development practices, including MVVM architecture, ViewBinding, DataBinding, Room database, and modern coroutine-based asynchronous operations.
- Automatically screens incoming calls using the CallScreeningService API.
- Blocks calls based on user-selected contacts from the app’s contact list.
- Skips call logs and notifications for blocked calls.
- Displays contacts fetched from the user’s device.
- Allows blocking/unblocking contacts directly from the app.
- Prevents duplicate contact entries using normalized phone numbers.
- Real-time search with debounced input handling.
- Displays a movable overlay with caller details for incoming calls.
- Uses the user’s input to block contacts dynamically, rather than relying on predefined numbers.
- Ensures that all necessary permissions are granted, including:
- READ_CONTACTS
- ANSWER_PHONE_CALLS
- READ_PHONE_STATE
- READ_CALL_LOG
- Dynamically requests permissions at runtime.
- Smooth animations for RecyclerView.
- Modern Material Design 3 theme with enhanced UI components.
- Overlay window with draggable capability for better user interaction.
- Includes comprehensive unit tests for ViewModel logic.
- Verifies LiveData updates, contact management, and search functionality.
- Android Studio: Ladybug 24.2.2 or later
- Gradle Version: 8.10.2
- Android Gradle Plugin (AGP): 8.8.0
- Permissions: Ensure runtime permissions are granted for app functionality.
- Clone the Repository
git clone https://github.com/Alims-Repo/CallerID.git
cd CallerID
- Open in Android Studio
- Launch Android Studio.
- Navigate to File > Open and select the CallerID project folder.
- Sync Gradle
- Gradle sync will start automatically. Ensure it completes successfully.
- Build and Run the Project
- Build the app using Ctrl+F9 or Build > Make Project.
- Run the app using Shift+F10 or the Run button.
- Grant Runtime Permissions
- On the first launch, the app will request permissions for contacts, call logs, and phone state. Grant all permissions for full functionality.
The app leverages both ViewBinding and DataBinding for efficient UI management:
- ViewBinding: Removes boilerplate code for UI component initialization.
- DataBinding: Allows dynamic updates of UI components using LiveData.
- RecyclerView Animations:
@BindingAdapter("itemAnimation")
fun setRecyclerViewAnimation(recyclerView: RecyclerView, enable: Boolean) {
if (enable) {
val context = recyclerView.context
val controller = AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_slide_in_bottom)
recyclerView.layoutAnimation = controller
recyclerView.scheduleLayoutAnimation()
}
}
- Animated Visibility:
@BindingAdapter("animateVisibility")
fun setAnimatedVisibility(view: View, isVisible: Boolean) {
if (isVisible) {
view.visibility = View.VISIBLE
view.alpha = 0f
view.animate().alpha(1f).setDuration(300).start()
} else {
view.visibility = View.GONE
}
}
- Dynamic Image Loading:
@BindingAdapter("imageUrl", "fallbackName", requireAll = false)
fun loadImage(view: ImageView, imageUrl: String?, fallbackName: String?) {
if (!imageUrl.isNullOrEmpty()) {
Glide.with(view.context).load(imageUrl).circleCrop().into(view)
} else if (!fallbackName.isNullOrEmpty()) {
view.setImageDrawable(generateLetterDrawable(view, fallbackName))
} else {
view.setImageResource(R.color.darker_gray)
}
}
- Utilizes the CallScreeningService API to intercept and handle incoming calls.
- Responds to calls dynamically, blocking unwanted numbers while showing overlays for allowed calls.
- Movable Overlay:
- Displays caller details in an interactive overlay.
- Users can drag the overlay window anywhere on the screen.
- JUnit: Unit testing framework.
- Mockito: Mocking library for repository testing.
- Coroutines Test: For testing coroutine-based ViewModel logic.
The test cases focus on validating the ViewModel logic, ensuring proper handling of LiveData updates, contact management, and error scenarios.
@Test
fun `contactMessage shows no contacts available`() = runTest {
// Arrange
whenever(repository.getAllDeviceContacts(null, null)).thenReturn(emptyList())
// Act
viewModel.loadContacts()
testDispatcher.scheduler.advanceUntilIdle()
// Assert
val message = viewModel.contactMessage.getOrAwaitValue()
Assert.assertEquals("No contacts to display. Add contacts to your list, and they’ll appear here.", message)
}
- Filter Matching Contacts:
- Ensures filtered results are displayed for valid queries.
- Dynamic Message Updates:
- Verifies filter message updates based on LiveData changes.