├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── attrs.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── logo.png │ │ │ │ ├── custom_info_bubble.9.png │ │ │ │ ├── bg_loading_dialog.xml │ │ │ │ ├── side_nav_bar.xml │ │ │ │ ├── bg_badge_num.xml │ │ │ │ ├── checkbox_selector.xml │ │ │ │ ├── ic_search.xml │ │ │ │ ├── ic_check_empty.xml │ │ │ │ ├── ic_check_selected.xml │ │ │ │ ├── ic_warning.xml │ │ │ │ ├── ic_shop.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── xml │ │ │ │ └── provider_paths.xml │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v21 │ │ │ │ ├── ic_menu_send.xml │ │ │ │ ├── ic_menu_slideshow.xml │ │ │ │ ├── ic_menu_gallery.xml │ │ │ │ ├── ic_menu_manage.xml │ │ │ │ ├── ic_menu_camera.xml │ │ │ │ └── ic_menu_share.xml │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── item.xml │ │ │ │ └── fragment_call_recoder_list.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── tntkhang │ │ │ │ ├── ui │ │ │ │ ├── BaseView.java │ │ │ │ ├── BasePresenter.java │ │ │ │ ├── call_records │ │ │ │ │ ├── CallRecordView.java │ │ │ │ │ ├── CallRecordPresenter.java │ │ │ │ │ ├── CallRecordAdapter.java │ │ │ │ │ └── CallRecordFragment.java │ │ │ │ ├── BaseFragment.java │ │ │ │ ├── BaseActivity.java │ │ │ │ └── main │ │ │ │ │ └── MainActivity.java │ │ │ │ ├── utils │ │ │ │ ├── Constants.java │ │ │ │ └── CommonMethods.java │ │ │ │ ├── di │ │ │ │ ├── AppComponent.java │ │ │ │ └── DatabaseModule.java │ │ │ │ ├── callrecorder │ │ │ │ ├── MyAccessibilityService.java │ │ │ │ ├── CallRecorderService.java │ │ │ │ └── PhoneStateReceiver.java │ │ │ │ ├── models │ │ │ │ └── database │ │ │ │ │ ├── dao │ │ │ │ │ └── CallDetailDAO.java │ │ │ │ │ ├── repository │ │ │ │ │ └── CallDetailRepository.java │ │ │ │ │ ├── AppDatabase.java │ │ │ │ │ └── entitiy │ │ │ │ │ └── CallDetailEntity.java │ │ │ │ └── BaseApplication.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── vn │ │ │ └── nextlogix │ │ │ └── tntkhang │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── vn │ │ └── nextlogix │ │ └── tntkhang │ │ └── ExampleInstrumentedTest.java ├── google-services.json ├── proguard-rules.pro ├── build.gradle └── app.iml ├── settings.gradle ├── app-debug.apk ├── Screenshot_1544935805.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── local.properties ├── CallRecordingMaster.iml ├── gradle.properties ├── call-recording-master.iml ├── README.md ├── gradlew.bat ├── .idea └── codeStyles │ └── Project.xml └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app-debug.apk -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Screenshot_1544935805.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/Screenshot_1544935805.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/drawable/logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/custom_info_bubble.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/drawable/custom_info_bubble.9.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tntkhang/call-recording-master/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Call Recording 3 | Call Recording Service 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/ui/BaseView.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.ui; 2 | 3 | import android.content.Context; 4 | 5 | public interface BaseView { 6 | void showProgressDialog(Context context); 7 | void hideProgressDialog(); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_loading_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Dec 12 16:27:34 ICT 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_badge_num.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Mon Nov 16 11:45:07 ICT 2020 8 | sdk.dir=/Users/tntkhang/Library/Android/sdk 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_send.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/checkbox_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #ffffff 7 | #40666666 8 | #80000000 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/ui/BasePresenter.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.ui; 2 | 3 | import io.reactivex.disposables.CompositeDisposable; 4 | 5 | public class BasePresenter { 6 | 7 | protected CompositeDisposable disposeBag; 8 | 9 | public BasePresenter() { 10 | disposeBag = new CompositeDisposable(); 11 | } 12 | 13 | public void onStop() { 14 | disposeBag.clear(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 8dp 6 | 176dp 7 | 16dp 8 | 16dp 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/ui/call_records/CallRecordView.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.ui.call_records; 2 | 3 | import java.util.List; 4 | 5 | import com.github.tntkhang.models.database.entitiy.CallDetailEntity; 6 | import com.github.tntkhang.ui.BaseView; 7 | 8 | public interface CallRecordView extends BaseView { 9 | 10 | void onGetAllEntities(List callDetailEntity); 11 | 12 | void onGetAllError(Throwable throwable); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/test/java/vn/nextlogix/tntkhang/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package vn.nextlogix.tntkhang; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.utils; 2 | 3 | public class Constants { 4 | 5 | public class Prefs { 6 | public static final String PHONE_NUMBER_TO_RECORD = "PHONE_NUMBER_TO_RECORD"; 7 | public static final String CALL_RECORD_STARTED = "CALL_RECORD_STARTED"; 8 | public static final String CALL_RECORD_PATH = "CALL_RECORD_PATH"; 9 | public static final String PHONE_CALL_NUMBER = "PHONE_CALL_NUMBER"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_slideshow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_gallery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/di/AppComponent.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.di; 2 | 3 | import com.github.tntkhang.ui.call_records.CallRecordFragment; 4 | 5 | import javax.inject.Singleton; 6 | 7 | import dagger.Component; 8 | import com.github.tntkhang.callrecorder.PhoneStateReceiver; 9 | 10 | @Singleton 11 | @Component(modules = {DatabaseModule.class}) 12 | public interface AppComponent { 13 | void inject(CallRecordFragment callRecordFragment); 14 | void inject(PhoneStateReceiver receiver); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_manage.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | -------------------------------------------------------------------------------- /CallRecordingMaster.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/ui/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.ui; 2 | 3 | import android.content.Context; 4 | import android.support.v4.app.Fragment; 5 | import android.widget.Toast; 6 | 7 | import com.github.tntkhang.di.AppComponent; 8 | import com.github.tntkhang.BaseApplication; 9 | 10 | public class BaseFragment extends Fragment { 11 | 12 | @Override 13 | public void onAttach(Context context) { 14 | super.onAttach(context); 15 | } 16 | 17 | public AppComponent getComponent() { 18 | return BaseApplication.getApplication().getAppComponent(); 19 | } 20 | 21 | 22 | public void showToast(Context context, String message) { 23 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/callrecorder/MyAccessibilityService.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.callrecorder; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.app.Service; 5 | import android.util.Log; 6 | import android.view.accessibility.AccessibilityEvent; 7 | 8 | public class MyAccessibilityService extends AccessibilityService { 9 | Service mService = null; 10 | 11 | @Override 12 | public void onAccessibilityEvent(AccessibilityEvent event) { 13 | } 14 | 15 | @Override 16 | public void onInterrupt() { 17 | 18 | } 19 | 20 | 21 | @Override 22 | protected void onServiceConnected() { 23 | } 24 | 25 | @Override 26 | public void onCreate() { 27 | this.mService = this; 28 | } 29 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/vn/nextlogix/tntkhang/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package vn.nextlogix.tntkhang; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("vn.nextlogix.nlshipper", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/models/database/dao/CallDetailDAO.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.models.database.dao; 2 | 3 | import android.arch.persistence.room.Dao; 4 | import android.arch.persistence.room.Delete; 5 | import android.arch.persistence.room.Insert; 6 | import android.arch.persistence.room.Query; 7 | import android.arch.persistence.room.Update; 8 | 9 | import com.github.tntkhang.models.database.entitiy.CallDetailEntity; 10 | 11 | import java.util.List; 12 | 13 | import io.reactivex.Flowable; 14 | 15 | @Dao 16 | public interface CallDetailDAO { 17 | 18 | @Query("SELECT * FROM call_detail ORDER BY uid DESC") 19 | Flowable> getAll(); 20 | 21 | @Insert 22 | void insert(CallDetailEntity callDetailEntity); 23 | 24 | @Update 25 | void update(CallDetailEntity callDetailEntity); 26 | 27 | @Delete 28 | void delete(CallDetailEntity callDetailEntity); 29 | } 30 | -------------------------------------------------------------------------------- /call-recording-master.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # call-recording-master 2 | This is an demo to help you to check which type of MediaRecorder.AudioSource that your phone support. Normally it's contains 4 types: DEFAULT, MIC, VOICE_CALL, VOICE_COMMUNICATION, base on android version of your phone that will support or unsupport some of them. 3 | 4 | So to make your app work prefectly with all android versions we should change the AudioSource type following the android version with this rule: 5 | ``` 6 | if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 7 | recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL); 8 | callType = "VOICE_CALL"; 9 | } else if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { 10 | recorder.setAudioSource(MediaRecorder.AudioSource.MIC); 11 | callType = "MIC"; 12 | } else { 13 | recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION); 14 | callType = "VOICE_COMMUNICATION"; 15 | } 16 | ``` 17 | 18 | ### Example APK: [app-debug.apk](app-debug.apk) 19 | 20 | ### Screenshot: ![APK](Screenshot_1544935805.png) 21 | 22 | # Checkout demo for detail. 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/BaseApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang; 2 | 3 | import android.app.Application; 4 | 5 | import com.github.tntkhang.di.AppComponent; 6 | import com.github.tntkhang.di.DaggerAppComponent; 7 | import com.github.tntkhang.di.DatabaseModule; 8 | 9 | import khangtran.preferenceshelper.PrefHelper; 10 | 11 | public class BaseApplication extends Application { 12 | AppComponent appComponent; 13 | private static BaseApplication INSTANCE; 14 | 15 | public static BaseApplication getApplication() { 16 | return INSTANCE; 17 | } 18 | 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | 23 | INSTANCE = this; 24 | initializeDependencies(); 25 | PrefHelper.initHelper(this); 26 | 27 | } 28 | 29 | private void initializeDependencies() { 30 | appComponent = DaggerAppComponent.builder() 31 | .databaseModule(new DatabaseModule(this)) 32 | .build(); 33 | } 34 | 35 | public AppComponent getAppComponent() { 36 | return appComponent; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "292466658017", 4 | "firebase_url": "https://nlshipper-74438.firebaseio.com", 5 | "project_id": "nlshipper-74438", 6 | "storage_bucket": "nlshipper-74438.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:292466658017:android:e501a106e70ff85e", 12 | "android_client_info": { 13 | "package_name": "vn.nextlogix.shipper.test" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "292466658017-i28tkuid4505ujebbvgnqqltss8l5np3.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyCY90CBaopvx4043eyKJjmovFKUvVna7nE" 25 | } 26 | ], 27 | "services": { 28 | "analytics_service": { 29 | "status": 1 30 | }, 31 | "appinvite_service": { 32 | "status": 1, 33 | "other_platform_oauth_client": [] 34 | }, 35 | "ads_service": { 36 | "status": 2 37 | } 38 | } 39 | } 40 | ], 41 | "configuration_version": "1" 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/models/database/repository/CallDetailRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.models.database.repository; 2 | 3 | import com.github.tntkhang.models.database.entitiy.CallDetailEntity; 4 | 5 | import java.util.List; 6 | import java.util.concurrent.Executor; 7 | 8 | import io.reactivex.Flowable; 9 | import com.github.tntkhang.models.database.dao.CallDetailDAO; 10 | 11 | public class CallDetailRepository { 12 | 13 | private CallDetailDAO callDetailDAO; 14 | private Executor executor; 15 | 16 | public CallDetailRepository(CallDetailDAO callDetailDAO, Executor exec) { 17 | this.callDetailDAO = callDetailDAO; 18 | executor = exec; 19 | } 20 | 21 | public Flowable> getAll() { 22 | return callDetailDAO.getAll(); 23 | } 24 | 25 | public void insert(CallDetailEntity callDetailEntity) { 26 | executor.execute(() -> callDetailDAO.insert(callDetailEntity)); 27 | } 28 | 29 | public void update(CallDetailEntity callDetailEntity) { 30 | executor.execute(() -> callDetailDAO.update(callDetailEntity)); 31 | } 32 | 33 | public void delete(CallDetailEntity callDetailEntity) { 34 | executor.execute(() -> callDetailDAO.delete(callDetailEntity)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/models/database/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.models.database; 2 | 3 | import android.arch.persistence.room.Database; 4 | import android.arch.persistence.room.Room; 5 | import android.arch.persistence.room.RoomDatabase; 6 | import android.content.Context; 7 | 8 | import com.github.tntkhang.models.database.dao.CallDetailDAO; 9 | import com.github.tntkhang.models.database.entitiy.CallDetailEntity; 10 | 11 | @Database(entities = {CallDetailEntity.class}, version = 1) 12 | public abstract class AppDatabase extends RoomDatabase { 13 | 14 | private static AppDatabase INSTANCE; 15 | 16 | public abstract CallDetailDAO callDetailDAO(); 17 | 18 | public static AppDatabase getAppDatabase(Context context) { 19 | if (INSTANCE == null) { 20 | INSTANCE = 21 | Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "user-database") 22 | // allow queries on the main thread. 23 | // Don't do this on a real app! See PersistenceBasicSample for an example. 24 | .allowMainThreadQueries() 25 | .build(); 26 | } 27 | return INSTANCE; 28 | } 29 | 30 | public static void destroyInstance() { 31 | INSTANCE = null; 32 | } 33 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | 24 | # - Start Google Direction Library 25 | -keep class com.google.android.gms.maps.** { *; } 26 | -keep interface com.google.android.gms.maps.* { *; } 27 | 28 | -dontwarn retrofit2.** 29 | -keep class retrofit2.** { *; } 30 | -keepattributes Signature 31 | -keepattributes Exceptions 32 | 33 | -keepclasseswithmembers class * { 34 | @retrofit2.http.* ; 35 | } 36 | # - End 37 | 38 | # - okhttp 39 | -dontwarn com.squareup.okhttp.** 40 | -dontwarn rx.** 41 | -dontwarn retrofit2.** 42 | -dontwarn okio.* 43 | #- End -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/ui/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.widget.Toast; 7 | 8 | import com.github.tntkhang.BaseApplication; 9 | import com.github.tntkhang.di.AppComponent; 10 | 11 | public class BaseActivity extends AppCompatActivity { 12 | 13 | public AppComponent getComponent() { 14 | return BaseApplication.getApplication().getAppComponent(); 15 | } 16 | 17 | public void showToast(String message) { 18 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); 19 | } 20 | 21 | public void startActivity(Context currentActivity, Class nextActivity) { 22 | Intent i = new Intent(currentActivity, nextActivity); 23 | startActivity(i); 24 | // overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); 25 | } 26 | 27 | @Override 28 | public void finish() { 29 | super.finish(); 30 | // overridePendingTransition(android.R.anim.fade_out, android.R.anim.fade_in); 31 | } 32 | 33 | public void finishWithoutAnim() { 34 | super.finish(); 35 | } 36 | 37 | @Override 38 | public void onBackPressed() { 39 | super.onBackPressed(); 40 | // overridePendingTransition(android.R.anim.fade_out, android.R.anim.fade_in); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/ui/call_records/CallRecordPresenter.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.ui.call_records; 2 | 3 | import java.util.List; 4 | 5 | import io.reactivex.android.schedulers.AndroidSchedulers; 6 | import io.reactivex.disposables.Disposable; 7 | import io.reactivex.schedulers.Schedulers; 8 | import com.github.tntkhang.models.database.entitiy.CallDetailEntity; 9 | import com.github.tntkhang.models.database.repository.CallDetailRepository; 10 | import com.github.tntkhang.ui.BasePresenter; 11 | 12 | public class CallRecordPresenter extends BasePresenter { 13 | private CallDetailRepository callDetailRepository; 14 | private CallRecordView mView; 15 | 16 | public CallRecordPresenter(CallDetailRepository callDetailRepository, CallRecordView view) { 17 | this.callDetailRepository = callDetailRepository; 18 | this.mView = view; 19 | } 20 | 21 | public void getAll() { 22 | Disposable disposable = callDetailRepository.getAll() 23 | .subscribeOn(Schedulers.computation()) 24 | .observeOn(AndroidSchedulers.mainThread()) 25 | .subscribe(this::onNextGetAllNotifications, this::onErrorGetAllNotifications); 26 | disposeBag.add(disposable); 27 | } 28 | 29 | 30 | 31 | private void onNextGetAllNotifications(List callDetailEntities) { 32 | mView.onGetAllEntities(callDetailEntities); 33 | } 34 | 35 | private void onErrorGetAllNotifications(Throwable throwable) { 36 | mView.onGetAllError(throwable); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/di/DatabaseModule.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.di; 2 | 3 | import android.arch.persistence.room.Room; 4 | import android.content.Context; 5 | 6 | import com.github.tntkhang.models.database.AppDatabase; 7 | import com.github.tntkhang.models.database.dao.CallDetailDAO; 8 | import com.github.tntkhang.models.database.repository.CallDetailRepository; 9 | 10 | import java.util.concurrent.Executor; 11 | import java.util.concurrent.Executors; 12 | 13 | import javax.inject.Singleton; 14 | 15 | import dagger.Module; 16 | import dagger.Provides; 17 | import vn.nextlogix.tntkhang.R; 18 | 19 | @Module 20 | public class DatabaseModule { 21 | 22 | private Context context; 23 | 24 | public DatabaseModule(Context ctx){ 25 | context = ctx; 26 | } 27 | 28 | @Provides 29 | @Singleton 30 | public Context provideContext() { 31 | return context; 32 | } 33 | 34 | @Singleton 35 | @Provides 36 | public Executor getExecutor(){ 37 | return Executors.newFixedThreadPool(2); 38 | } 39 | 40 | @Provides 41 | @Singleton 42 | public AppDatabase getAppDatabase(Context context){ 43 | return Room.databaseBuilder(context, 44 | AppDatabase.class, 45 | context.getString(R.string.app_name)).build(); 46 | } 47 | 48 | @Provides 49 | @Singleton 50 | public CallDetailDAO provideCallDetailDAO(AppDatabase appDatabase){ 51 | return appDatabase.callDetailDAO(); 52 | } 53 | 54 | @Provides 55 | @Singleton 56 | public CallDetailRepository provideCallDetailRepository(CallDetailDAO callDetailDAO, Executor exec){ 57 | return new CallDetailRepository(callDetailDAO, exec); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/utils/CommonMethods.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.utils; 2 | 3 | import android.os.Environment; 4 | import android.util.Log; 5 | 6 | import java.io.File; 7 | import java.util.Calendar; 8 | 9 | public class CommonMethods { 10 | 11 | static final String TAGCM = "Inside Service"; 12 | 13 | public static String getDate() { 14 | Calendar cal = Calendar.getInstance(); 15 | int year = cal.get(Calendar.YEAR); 16 | int month = cal.get(Calendar.MONTH) + 1; 17 | int day = cal.get(Calendar.DATE); 18 | String date = String.valueOf(day) + "/" + String.valueOf(month) + "/" + String.valueOf(year); 19 | 20 | Log.d(TAGCM, "Date " + date); 21 | return date; 22 | } 23 | 24 | 25 | public static String getTime() { 26 | Calendar cal = Calendar.getInstance(); 27 | int sec = cal.get(Calendar.SECOND); 28 | int min = cal.get(Calendar.MINUTE); 29 | int hr = cal.get(Calendar.HOUR_OF_DAY); 30 | 31 | String time = String.valueOf(hr) + String.valueOf(min) + String.valueOf(sec); 32 | 33 | Log.d(TAGCM, "Date " + time); 34 | return time; 35 | } 36 | 37 | public static String getClearTime() { 38 | Calendar cal = Calendar.getInstance(); 39 | int sec = cal.get(Calendar.SECOND); 40 | int min = cal.get(Calendar.MINUTE); 41 | int hr = cal.get(Calendar.HOUR_OF_DAY); 42 | 43 | String time = String.valueOf(hr) + ":" + String.valueOf(min) + ":" + String.valueOf(sec); 44 | 45 | Log.d(TAGCM, "Date " + time); 46 | return time; 47 | } 48 | 49 | public static String getPath() { 50 | File file = new File(Environment.getExternalStorageDirectory() + "/ANLShipper/"); 51 | if (!file.exists()) { 52 | file.mkdir(); 53 | } 54 | String path = file.getAbsolutePath(); 55 | Log.d(TAGCM, "Path " + path); 56 | 57 | return path; 58 | } 59 | 60 | public static String removeSpaceInPhoneNo(String phoneNo) { 61 | return phoneNo.replace(" ", ""); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/models/database/entitiy/CallDetailEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.models.database.entitiy; 2 | 3 | import android.arch.persistence.room.ColumnInfo; 4 | import android.arch.persistence.room.Entity; 5 | import android.arch.persistence.room.Ignore; 6 | import android.arch.persistence.room.PrimaryKey; 7 | 8 | @Entity(tableName = "call_detail") 9 | public class CallDetailEntity { 10 | 11 | @PrimaryKey(autoGenerate = true) 12 | private int uid; 13 | 14 | @ColumnInfo(name = "phone_number") 15 | private String phoneNumber; 16 | 17 | @ColumnInfo(name = "contact_name") 18 | private String contactName; 19 | 20 | @ColumnInfo(name = "time") 21 | private String time; 22 | 23 | @ColumnInfo(name = "date") 24 | private String date; 25 | 26 | @ColumnInfo(name = "record_path") 27 | private String recordPath; 28 | 29 | public CallDetailEntity(){ 30 | 31 | } 32 | 33 | @Ignore 34 | public CallDetailEntity(String phoneNumber, String contactName, String time, String date, String recordPath) { 35 | this.phoneNumber = phoneNumber; 36 | this.contactName = contactName; 37 | this.time = time; 38 | this.date = date; 39 | this.recordPath = recordPath; 40 | } 41 | 42 | public void setUid(int uid) { 43 | this.uid = uid; 44 | } 45 | 46 | public int getUid() { 47 | return uid; 48 | } 49 | 50 | public String getPhoneNumber() { 51 | return phoneNumber; 52 | } 53 | 54 | public void setPhoneNumber(String phoneNumber) { 55 | this.phoneNumber = phoneNumber; 56 | } 57 | 58 | public String getContactName() { 59 | return contactName; 60 | } 61 | 62 | public void setContactName(String contactName) { 63 | this.contactName = contactName; 64 | } 65 | 66 | public String getTime() { 67 | return time; 68 | } 69 | 70 | public void setTime(String time) { 71 | this.time = time; 72 | } 73 | 74 | public String getDate() { 75 | return date; 76 | } 77 | 78 | public void setDate(String date) { 79 | this.date = date; 80 | } 81 | 82 | public String getRecordPath() { 83 | return recordPath; 84 | } 85 | 86 | public void setRecordPath(String recordPath) { 87 | this.recordPath = recordPath; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_warning.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | signingConfigs { 5 | debug { 6 | } 7 | } 8 | compileSdkVersion 27 9 | defaultConfig { 10 | applicationId "com.github.tntkhang.callrecording" 11 | minSdkVersion 19 12 | targetSdkVersion 27 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | javaCompileOptions { 18 | annotationProcessorOptions { 19 | arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] 20 | } 21 | } 22 | } 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | debug { 29 | signingConfig signingConfigs.debug 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation fileTree(include: ['*.jar'], dir: 'libs') 40 | implementation 'com.android.support:appcompat-v7:27.1.1' 41 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 42 | implementation 'com.android.support:design:27.1.1' 43 | implementation 'com.android.support:recyclerview-v7:27.1.1' 44 | implementation 'com.android.support:cardview-v7:27.1.1' 45 | implementation 'com.android.support:support-v4:27.1.1' 46 | testImplementation 'junit:junit:4.12' 47 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 48 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 49 | implementation 'com.android.support:multidex:1.0.3' 50 | 51 | // Dagger 52 | implementation 'com.google.dagger:dagger:2.16' 53 | implementation 'com.google.dagger:dagger-android:2.15' 54 | implementation 'com.google.dagger:dagger-android-support:2.15' 55 | annotationProcessor "com.google.dagger:dagger-compiler:2.15" 56 | annotationProcessor "com.google.dagger:dagger-android-processor:2.15" 57 | compileOnly 'org.glassfish:javax.annotation:10.0-b28' 58 | 59 | // SharePreferences 60 | implementation 'com.github.tntkhang:preferences-helper:1.3.0' 61 | 62 | //Butter Knife 63 | implementation 'com.jakewharton:butterknife:8.8.1' 64 | annotationProcessor "com.jakewharton:butterknife-compiler:8.8.1" 65 | 66 | // RxJava 67 | implementation 'io.reactivex.rxjava2:rxjava:2.2.3' 68 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' 69 | 70 | // Unit sdp ssp 71 | implementation 'com.intuit.sdp:sdp-android:1.0.5' 72 | implementation 'com.intuit.ssp:ssp-android:1.0.5' 73 | 74 | // Room 75 | implementation "android.arch.persistence.room:rxjava2:1.1.1" 76 | implementation 'android.arch.persistence.room:runtime:1.1.1' 77 | annotationProcessor "android.arch.persistence.room:compiler:1.1.1" 78 | 79 | 80 | // check permission device 81 | implementation 'com.karumi:dexter:5.0.0' 82 | 83 | implementation 'com.github.tntkhang:gmail-sender-library:1.2.0' 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/ui/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.ui.main; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.preference.PreferenceManager; 9 | import android.provider.Settings; 10 | import android.support.annotation.Nullable; 11 | import android.support.v4.app.Fragment; 12 | import android.support.v4.app.FragmentManager; 13 | import android.support.v4.app.FragmentTransaction; 14 | import android.widget.Toast; 15 | 16 | import com.github.tntkhang.callrecorder.CallRecorderService; 17 | import com.github.tntkhang.callrecorder.MyAccessibilityService; 18 | import com.github.tntkhang.ui.BaseActivity; 19 | import com.github.tntkhang.ui.call_records.CallRecordFragment; 20 | import com.github.tntkhang.utils.Constants; 21 | import com.karumi.dexter.Dexter; 22 | import com.karumi.dexter.MultiplePermissionsReport; 23 | import com.karumi.dexter.PermissionToken; 24 | import com.karumi.dexter.listener.PermissionRequest; 25 | import com.karumi.dexter.listener.multi.MultiplePermissionsListener; 26 | 27 | import java.util.List; 28 | 29 | import butterknife.ButterKnife; 30 | import vn.nextlogix.tntkhang.R; 31 | 32 | public class MainActivity extends BaseActivity { 33 | public static int ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE= 2323; 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_main); 38 | ButterKnife.bind(this); 39 | requestPermissions(); 40 | 41 | Fragment recordFragment = CallRecordFragment.newInstance(); 42 | 43 | replaceFragment(recordFragment); 44 | 45 | // 46 | // Intent accessService = new Intent(this, MyAccessibilityService.class); 47 | // 48 | // startService(accessService); 49 | } 50 | 51 | private void requestPermissions() { 52 | Dexter.withActivity(this) 53 | .withPermissions( 54 | Manifest.permission.RECORD_AUDIO, 55 | Manifest.permission.READ_EXTERNAL_STORAGE, 56 | Manifest.permission.READ_PHONE_STATE, 57 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 58 | Manifest.permission.CALL_PHONE, 59 | Manifest.permission.READ_CALL_LOG 60 | ).withListener(new MultiplePermissionsListener() { 61 | @Override 62 | public void onPermissionsChecked(MultiplePermissionsReport report) { 63 | } 64 | 65 | @Override 66 | public void onPermissionRationaleShouldBeShown(List permissions, PermissionToken token) { 67 | } 68 | }) 69 | .check(); 70 | } 71 | 72 | 73 | private void replaceFragment(Fragment fragment) { 74 | FragmentManager fm = getSupportFragmentManager(); 75 | FragmentTransaction fragmentTransaction = fm.beginTransaction(); 76 | fragmentTransaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out); 77 | fragmentTransaction.replace(R.id.container, fragment); 78 | fragmentTransaction.commit(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 65 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/callrecorder/CallRecorderService.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.callrecorder; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.media.MediaRecorder; 6 | import android.os.Build; 7 | import android.os.IBinder; 8 | import android.support.annotation.Nullable; 9 | import android.util.Log; 10 | import android.widget.Toast; 11 | 12 | import com.github.tntkhang.utils.Constants; 13 | 14 | public class CallRecorderService extends Service { 15 | 16 | MediaRecorder recorder; 17 | static final String TAGS = "tntkhang"; 18 | 19 | private boolean isStartRecordSuccess = true; 20 | @Nullable 21 | @Override 22 | public IBinder onBind(Intent intent) { 23 | return null; 24 | } 25 | 26 | public int onStartCommand(Intent intent, int flags, int startId) { 27 | recorder = new MediaRecorder(); 28 | recorder.reset(); 29 | 30 | String phoneNumber = intent.getStringExtra(Constants.Prefs.PHONE_CALL_NUMBER); 31 | String outputPath = intent.getStringExtra(Constants.Prefs.CALL_RECORD_PATH); 32 | Log.d(TAGS, "Phone number in service: " + phoneNumber); 33 | 34 | // if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 35 | // recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL); 36 | // } else if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { 37 | // recorder.setAudioSource(MediaRecorder.AudioSource.MIC); 38 | // } else { 39 | // recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION); 40 | // } 41 | 42 | recorder.setAudioSamplingRate(44100); 43 | recorder.setAudioEncodingBitRate(96000); 44 | 45 | 46 | recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION); 47 | // recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION); 48 | recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 49 | recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 50 | 51 | // recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION); 52 | // recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); 53 | // recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 54 | 55 | 56 | // recorder.setAudioSource(MediaRecorder.AudioSource.MIC); 57 | // recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 58 | // recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 59 | 60 | 61 | // recorder.setAudioSource(MediaRecorder.AudioSource.MIC); 62 | // recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 63 | // recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 64 | 65 | recorder.setOutputFile(outputPath); 66 | 67 | try { 68 | recorder.prepare(); 69 | recorder.start(); 70 | } catch (Exception e) { 71 | isStartRecordSuccess = false; 72 | e.printStackTrace(); 73 | } 74 | return START_NOT_STICKY; 75 | } 76 | 77 | public void onDestroy() { 78 | super.onDestroy(); 79 | if (isStartRecordSuccess) { 80 | try { 81 | if (recorder != null) { 82 | recorder.stop(); 83 | recorder.reset(); 84 | recorder.release(); 85 | recorder = null; 86 | } 87 | } catch (Exception e) { 88 | e.printStackTrace(); 89 | } 90 | Log.d(TAGS, "onDestroy: " + "Recording stopped"); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/callrecorder/PhoneStateReceiver.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.callrecorder; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.telephony.TelephonyManager; 8 | import android.util.Log; 9 | 10 | import com.github.tntkhang.BaseApplication; 11 | import com.github.tntkhang.models.database.entitiy.CallDetailEntity; 12 | import com.github.tntkhang.models.database.repository.CallDetailRepository; 13 | import com.github.tntkhang.utils.CommonMethods; 14 | import com.github.tntkhang.utils.Constants; 15 | 16 | import javax.inject.Inject; 17 | 18 | import khangtran.preferenceshelper.PrefHelper; 19 | 20 | public class PhoneStateReceiver extends BroadcastReceiver { 21 | 22 | @Inject 23 | CallDetailRepository callDetailRepository; 24 | 25 | @Override 26 | public void onReceive(Context context, Intent intent) { 27 | Log.i("tntkhang", "PhoneStateReceiver - onReceive"); 28 | BaseApplication.getApplication().getAppComponent().inject(this); 29 | try { 30 | Bundle extras = intent.getExtras(); 31 | String state = extras.getString(TelephonyManager.EXTRA_STATE); 32 | 33 | if (state.equals(TelephonyManager.EXTRA_STATE_RINGING)) { 34 | } else if (state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) { 35 | String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); 36 | String recPath = startRecording(context, phoneNumber); 37 | Log.i("tntkhang", "recPath : " + recPath); 38 | 39 | PrefHelper.setVal(Constants.Prefs.CALL_RECORD_STARTED, !recPath.isEmpty()); 40 | } else if (state.equals(TelephonyManager.EXTRA_STATE_IDLE)) { 41 | if (PrefHelper.getBooleanVal(Constants.Prefs.CALL_RECORD_STARTED, false)) { 42 | String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); 43 | stopRecording(context, phoneNumber); 44 | } 45 | } 46 | Log.i("tntkhang", "PhoneStateReceiver - onReceive: " + state); 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | 52 | private String startRecording(Context context, String number) { 53 | String trimNumber = CommonMethods.removeSpaceInPhoneNo(number); 54 | Log.i("tntkhang", "startRecording - trimNumber: " + trimNumber); 55 | 56 | String time = CommonMethods.getTime(); 57 | String date = CommonMethods.getDate(); 58 | String path = CommonMethods.getPath(); 59 | String outputPath = path + "/" + trimNumber + "_" + time + ".mp3"; 60 | // String outputPath = path + "/" + trimNumber + "_" + time + ".mp4"; 61 | Log.i("tntkhang", "outputPath: " + outputPath); 62 | 63 | Intent recordService = new Intent(context, CallRecorderService.class); 64 | recordService.putExtra(Constants.Prefs.PHONE_CALL_NUMBER, trimNumber); 65 | recordService.putExtra(Constants.Prefs.CALL_RECORD_PATH, outputPath); 66 | 67 | context.startService(recordService); 68 | 69 | String name = ""; 70 | CallDetailEntity callDetailEntity = new CallDetailEntity(trimNumber, name, time, date, outputPath); 71 | callDetailRepository.insert(callDetailEntity); 72 | return outputPath; 73 | } 74 | 75 | private void stopRecording(Context context, String phoneNo) { 76 | Log.i("tntkhang", "stopRecording: " + phoneNo); 77 | context.stopService(new Intent(context, CallRecorderService.class)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | xmlns:android 11 | 12 | ^$ 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | xmlns:.* 22 | 23 | ^$ 24 | 25 | 26 | BY_NAME 27 | 28 |
29 |
30 | 31 | 32 | 33 | .*:id 34 | 35 | http://schemas.android.com/apk/res/android 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | .*:name 45 | 46 | http://schemas.android.com/apk/res/android 47 | 48 | 49 | 50 |
51 |
52 | 53 | 54 | 55 | name 56 | 57 | ^$ 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | style 67 | 68 | ^$ 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | .* 78 | 79 | ^$ 80 | 81 | 82 | BY_NAME 83 | 84 |
85 |
86 | 87 | 88 | 89 | .* 90 | 91 | http://schemas.android.com/apk/res/android 92 | 93 | 94 | ANDROID_ATTRIBUTE_ORDER 95 | 96 |
97 |
98 | 99 | 100 | 101 | .* 102 | 103 | .* 104 | 105 | 106 | BY_NAME 107 | 108 |
109 |
110 |
111 |
112 |
113 |
-------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shop.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 46 | 49 | 52 | 55 | 58 | 61 | 64 | 67 | 70 | 73 | 76 | 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tntkhang/ui/call_records/CallRecordAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.tntkhang.ui.call_records; 2 | 3 | import android.content.Intent; 4 | import android.support.v4.content.ContextCompat; 5 | import android.support.v7.app.AlertDialog; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import java.io.File; 15 | import java.util.List; 16 | 17 | import butterknife.BindView; 18 | import butterknife.ButterKnife; 19 | import vn.nextlogix.tntkhang.R; 20 | import com.github.tntkhang.models.database.entitiy.CallDetailEntity; 21 | import com.github.tntkhang.models.database.repository.CallDetailRepository; 22 | 23 | import static android.support.v4.content.FileProvider.getUriForFile; 24 | 25 | public class CallRecordAdapter extends RecyclerView.Adapter { 26 | 27 | private final List callDetailEntities; 28 | private CallDetailRepository callDetailRepository; 29 | 30 | public CallRecordAdapter(CallDetailRepository callDetailRepository, List callDetailEntities) { 31 | this.callDetailRepository = callDetailRepository; 32 | this.callDetailEntities = callDetailEntities; 33 | } 34 | 35 | @Override 36 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | View view = LayoutInflater.from(parent.getContext()) 38 | .inflate(R.layout.item, parent, false); 39 | return new ViewHolder(view); 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(final ViewHolder holder, int position) { 44 | holder.mItem = callDetailEntities.get(position); 45 | holder.mIdView.setText(holder.mItem.getPhoneNumber() + (!holder.mItem.getContactName().isEmpty() ? " - " + holder.mItem.getContactName() : "")); 46 | holder.mContentView.setText(holder.mItem.getTime() + " - " + holder.mItem.getDate()); 47 | 48 | holder.lnContainer.setOnClickListener( v -> { 49 | Intent intent = new Intent(Intent.ACTION_VIEW); 50 | File file = new File(holder.mItem.getRecordPath()); 51 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 52 | intent.setDataAndType(getUriForFile(holder.mView.getContext(), "com.github.tntkhang.callrecorder",file), "audio/*"); 53 | holder.mView.getContext().startActivity(intent); 54 | }); 55 | 56 | holder.lnContainer.setOnLongClickListener(view -> { 57 | new AlertDialog.Builder(holder.mView.getContext()) 58 | .setIcon(ContextCompat.getDrawable(holder.mView.getContext(), R.drawable.ic_warning)) 59 | .setMessage("Delete this file ?") 60 | .setPositiveButton("Yes", (dialog, which) -> { 61 | File audioFile = new File(holder.mItem.getRecordPath()); 62 | if (audioFile.exists()) { 63 | if (audioFile.delete()) { 64 | removeItem(holder.mItem); 65 | callDetailRepository.delete(holder.mItem); 66 | } else { 67 | Toast.makeText(holder.mView.getContext(), "Delete fail", Toast.LENGTH_SHORT).show(); 68 | } 69 | } else { 70 | Toast.makeText(holder.mView.getContext(), "File not exist", Toast.LENGTH_SHORT).show(); 71 | } 72 | }) 73 | .setNegativeButton("No", null) 74 | .show(); 75 | return true; 76 | }); 77 | } 78 | 79 | private void removeItem(CallDetailEntity callDetailEntity) { 80 | int position = callDetailEntities.indexOf(callDetailEntity); 81 | callDetailEntities.remove(callDetailEntity); 82 | notifyItemRemoved(position); 83 | } 84 | 85 | @Override 86 | public int getItemCount() { 87 | return callDetailEntities.size(); 88 | } 89 | 90 | public class ViewHolder extends RecyclerView.ViewHolder { 91 | View mView; 92 | 93 | @BindView(R.id.container) 94 | LinearLayout lnContainer; 95 | 96 | @BindView(R.id.item_number) 97 | TextView mIdView; 98 | 99 | @BindView(R.id.content) 100 | TextView mContentView; 101 | 102 | CallDetailEntity mItem; 103 | 104 | ViewHolder(View view) { 105 | super(view); 106 | ButterKnife.bind(this, view); 107 | mView = view; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_call_recoder_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 15 | 20 | 25 | 26 | 32 | 38 | 44 | 50 | 51 |