= HashMap()
11 | private const val DURATION = 4000L
12 |
13 | fun show(context: Context, msg: Int) {
14 | show(context, context.getText(msg))
15 | }
16 |
17 | fun show(context: Context, msg: CharSequence) {
18 | handler.post {
19 | checkExpires()
20 | val toast = Toast.makeText(context, msg, Toast.LENGTH_LONG)
21 | map[toast] = System.currentTimeMillis()
22 | val delay = DURATION * (map.size - 1)
23 | handler.postDelayed({ toast.show() }, delay)
24 | }
25 | }
26 |
27 | private fun checkExpires() {
28 | with(map.entries.iterator()) {
29 | forEach {
30 | if (it.value < System.currentTimeMillis() - DURATION) {
31 | remove()
32 | }
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/res/raw-zh/privacy_notice.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
19 | Privacy Notice
20 |
21 |
22 |
23 | 隐私权声明
24 |
25 |
26 |
来电信息是一个 GitHub 开放源代码项目, 我们承诺不会收集您的任何隐私信息。
27 |
28 |
联系人信息使用的说明
29 |
根据您的设置, 我们仅在本地使用您的联系人信息来略过查找号码的过程, 此信息不会分享给任何人。
30 |
31 |
电话状态信息使用的说明
32 |
我们使用电话状态信息来获取您的来电号码或去电号码,来电或去电号码可能会被发送给百度或聚合数据(360)用于查询号码信息, 但是它并没有关联到您的手机号码或您的任何个人信息。
33 |
34 |
通话记录信息使用的说明
35 |
根据您的设置, 我们仅在本地读写您的通话记录信息, 此信息不会分享给任何人。
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/plugin/src/main/res/raw-zh/privacy_notice.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
19 | Privacy Notice
20 |
21 |
22 |
23 | 隐私权声明
24 |
25 |
26 |
来电信息是一个 GitHub 开放源代码项目, 我们承诺不会收集您的任何隐私信息。
27 |
28 |
联系人信息使用的说明
29 |
根据您的设置, 我们仅在本地使用您的联系人信息来略过查找号码的过程, 此信息不会分享给任何人。
30 |
31 |
电话状态信息使用的说明
32 |
我们使用电话状态信息来获取您的来电号码或去电号码,来电或去电号码可能会被发送给百度或聚合数据(360)用于查询号码信息, 但是它并没有关联到您的手机号码或您的任何个人信息。
33 |
34 |
通话记录信息使用的说明
35 |
根据您的设置, 我们仅在本地读写您的通话记录信息, 此信息不会分享给任何人。
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/log/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 29
7 | buildToolsVersion "29.0.2"
8 |
9 | defaultConfig {
10 | minSdkVersion 16
11 | targetSdkVersion 29
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles 'consumer-rules.pro'
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(dir: 'libs', include: ['*.jar'])
30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
31 | implementation 'androidx.appcompat:appcompat:1.1.0'
32 | implementation 'androidx.core:core-ktx:1.2.0'
33 | testImplementation 'junit:junit:4.12'
34 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
35 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_call_disconnected_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/activity/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.activity
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.MenuItem
6 | import androidx.appcompat.app.AppCompatActivity
7 | import org.xdty.callerinfo.R
8 | import org.xdty.callerinfo.fragment.SettingsFragment
9 | import org.xdty.callerinfo.utils.Utils
10 |
11 | class SettingsActivity : AppCompatActivity() {
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setTitle(R.string.action_settings)
15 | val actionBar = supportActionBar
16 | actionBar?.setDisplayHomeAsUpEnabled(true)
17 | if (savedInstanceState == null) {
18 | fragmentManager.beginTransaction()
19 | .add(android.R.id.content, SettingsFragment.newInstance(intent))
20 | .commit()
21 | }
22 | }
23 |
24 | override fun attachBaseContext(newBase: Context) {
25 | val context: Context = Utils.changeLang(newBase)
26 | super.attachBaseContext(context)
27 | }
28 |
29 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
30 | val id = item.itemId
31 | if (id == android.R.id.home) {
32 | finish()
33 | }
34 | return true
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/float_window.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
14 |
15 |
23 |
24 |
25 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/art/phone_auricular_with-a_cross_sign.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/plugin/src/main/java/org/xdty/callerinfo/plugin/Utils.java:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.plugin;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 |
7 | public class Utils {
8 |
9 | static void hideIcon(Context context) {
10 | PackageManager p = context.getPackageManager();
11 | ComponentName componentName = new ComponentName(context,
12 | context.getPackageName() + ".Launcher");
13 | p.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
14 | PackageManager.DONT_KILL_APP);
15 | }
16 |
17 | static void showIcon(Context context) {
18 | PackageManager p = context.getPackageManager();
19 | ComponentName componentName = new ComponentName(context,
20 | context.getPackageName() + ".Launcher");
21 | p.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
22 | PackageManager.DONT_KILL_APP);
23 | }
24 |
25 | static boolean isAppInstalled(Context context, String packageName) {
26 | try {
27 | context.getPackageManager().getApplicationInfo(packageName, 0);
28 | return true;
29 | } catch (PackageManager.NameNotFoundException e) {
30 | return false;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/settings/dialog/CustomApiDialog.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.settings.dialog
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import android.view.View
6 | import android.widget.EditText
7 | import org.xdty.callerinfo.R
8 |
9 | class CustomApiDialog(context: Context, sharedPrefs: SharedPreferences) : SettingsDialog(context, sharedPrefs) {
10 |
11 | private lateinit var apiUri: EditText
12 | private lateinit var apiKey: EditText
13 |
14 | private lateinit var customApiKey: String
15 |
16 | override fun bindViews() {
17 | val layout = View.inflate(context, R.layout.dialog_custom_api, null)
18 | builder.setView(layout)
19 |
20 | apiUri = layout.findViewById(R.id.api_uri)
21 | apiKey = layout.findViewById(R.id.api_key)
22 |
23 | customApiKey = context.getString(R.string.custom_api_key)
24 |
25 | apiUri.setText(sharedPrefs.getString(key, ""))
26 | apiKey.setText(sharedPrefs.getString(customApiKey, ""))
27 | }
28 |
29 | override fun onConfirm() {
30 | val value: String = apiUri.text.toString()
31 | val key: String = apiKey.text.toString()
32 | val editor = sharedPrefs.edit()
33 | editor.putString(key, value)
34 | editor.putString(customApiKey, key)
35 | editor.apply()
36 |
37 | super.onConfirm(value)
38 | }
39 | }
--------------------------------------------------------------------------------
/.travis/env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script generates environment variables for pull requests and forks.
4 |
5 | VERSION_NAME="$(git describe --abbrev=0 --tags)"
6 | export VERSION_NAME="${VERSION_NAME//v/}"
7 | export VERSION_CODE="$(git rev-list --tags --no-walk --count)"
8 |
9 | if [ -z "$encrypted_75846693d905_key" ] ; then
10 | # It's running from pull requests or forks, set vars.
11 |
12 | TEXT="I_AM_PUBLIC_AND_NOT_USED_FOR_RELEASE"
13 |
14 | # encrypted key and iv is taking from 'openssl enc -nosalt -aes-256-cbc -pass pass:I_AM_PUBLIC_AND_NOT_USED_FOR_RELEASE -P'
15 |
16 | export encrypted_75846693d905_key="12CF1B5E0D192628AA922230549EEDFD889E6CF7463933C6DABD9A1300FCA23D"
17 | export encrypted_75846693d905_iv="66813CF28D04CD129D57436B78DECBA4"
18 |
19 | export GITHUB_TOKEN="$TEXT"
20 | export KEYSTORE_PASSWORD="$TEXT"
21 | export ALIAS_PASSWORD="$TEXT"
22 | export ALIAS="$TEXT"
23 | export API_KEY="$TEXT"
24 | export JUHE_API_KEY="$TEXT"
25 | export LEANCLOUD_APP_ID="$TEXT"
26 | export LEANCLOUD_APP_KEY="$TEXT"
27 |
28 | # Overlay secrets.tar.enc
29 |
30 | # Travis-ci is using 'openssl aes-256-cbc -K 12CF1B5E0D192628AA922230549EEDFD889E6CF7463933C6DABD9A1300FCA23D -iv 66813CF28D04CD129D57436B78DECBA4 -in public.tar.enc -out public.tar -d' to decrypt the file.
31 | mv "public.tar.enc" "secrets.tar.enc"
32 | fi
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
26 |
27 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/signing.gradle:
--------------------------------------------------------------------------------
1 | def signingProperties = "signing.properties"
2 | def signingKeys = [
3 | storeFile : { x -> rootProject.file(x) },
4 | storePassword: { x -> x },
5 | keyAlias : { x -> x },
6 | keyPassword : { x -> x },
7 | ]
8 |
9 | // Find signing.properties in project root, or in $HOME/.gradle
10 | def f = ["${rootDir}/${signingProperties}", "${gradle.gradleUserHomeDir}/${signingProperties}"].find {
11 | file(it).exists()
12 | }
13 |
14 | if (f) {
15 | logger.info "Loading signing properties from ${f}"
16 | def props = new Properties()
17 | props.load(new FileInputStream(f))
18 |
19 | // For each property apply it to the release signing config
20 | signingKeys.any { k, fn ->
21 | if (!props.containsKey(k)) {
22 | logger.error "Missing property ${k}"
23 | android.buildTypes.release.signingConfig = null
24 | return true
25 | }
26 | android.signingConfigs.release[k] = fn(props[k])
27 | logger.info "Setting property ${k}"
28 | }
29 | } else {
30 | logger.info "Missing ${signingProperties} file"
31 | android.signingConfigs.release["storeFile"] = rootProject.file("release.jks")
32 | android.signingConfigs.release["storePassword"] = "${System.env.KEYSTORE_PASSWORD}"
33 | android.signingConfigs.release["keyAlias"] = "${System.env.ALIAS}"
34 | android.signingConfigs.release["keyPassword"] = "${System.env.ALIAS_PASSWORD}"
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/settings/dialog/EditDialog.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.settings.dialog
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import android.text.InputType
6 | import android.view.View
7 | import android.widget.EditText
8 | import org.xdty.callerinfo.R
9 |
10 | class EditDialog(context: Context, sharedPreferences: SharedPreferences) : SettingsDialog(context, sharedPreferences) {
11 |
12 | private lateinit var editText: EditText
13 |
14 | override fun bindViews() {
15 | val layout = View.inflate(context, R.layout.dialog_edit, null)
16 | builder.setView(layout)
17 | editText = layout.findViewById(R.id.text)
18 | if (defaultText > 0) {
19 | editText.setText(sharedPrefs.getString(key, context.getString(defaultText)))
20 | } else {
21 | editText.setText(sharedPrefs.getString(key, ""))
22 | }
23 | editText.setInputType(InputType.TYPE_CLASS_TEXT)
24 | if (hint > 0) {
25 | editText.setHint(hint)
26 | }
27 | }
28 |
29 | override fun onConfirm() {
30 | var value = editText.text.toString()
31 |
32 | if (value.isEmpty() && defaultText != 0) {
33 | value = context.getString(defaultText)
34 | }
35 |
36 | val editor = sharedPrefs.edit()
37 | editor.putString(key, value)
38 | editor.apply()
39 |
40 | super.onConfirm(value)
41 | }
42 | }
--------------------------------------------------------------------------------
/manifest.gradle:
--------------------------------------------------------------------------------
1 | def manifestProperties = "manifest.properties"
2 | def manifestKeys = [
3 | API_KEY : { x -> x },
4 | JUHE_API_KEY: { x -> x },
5 | LEANCLOUD_APP_ID: { x -> x },
6 | LEANCLOUD_APP_KEY: { x -> x }
7 | ]
8 |
9 | // Find manifest.properties in project root, or in $HOME/.gradle
10 | def f = ["${rootDir}/${manifestProperties}", "${gradle.gradleUserHomeDir}/${manifestProperties}"].find {
11 | file(it).exists()
12 | }
13 |
14 | if (f) {
15 | logger.info "Loading manifest properties from ${f}"
16 | def props = new Properties()
17 | props.load(new FileInputStream(f))
18 |
19 | // For each property apply it to the release manifest config
20 | manifestKeys.any { k, fn ->
21 | if (!props.containsKey(k)) {
22 | logger.error "Missing property ${k}"
23 | android.defaultConfig.manifestPlaceholders = null
24 | return true
25 | }
26 | android.defaultConfig.manifestPlaceholders[k] = fn(props[k])
27 | logger.info "Setting property ${k}"
28 | }
29 | } else {
30 | logger.info "Missing ${manifestProperties} file"
31 | android.defaultConfig.manifestPlaceholders["API_KEY"] = "${System.env.API_KEY}"
32 | android.defaultConfig.manifestPlaceholders["JUHE_API_KEY"] = "${System.env.JUHE_API_KEY}"
33 | android.defaultConfig.manifestPlaceholders["LEANCLOUD_APP_ID"] = "${System.env.LEANCLOUD_APP_ID}"
34 | android.defaultConfig.manifestPlaceholders["LEANCLOUD_APP_KEY"] = "${System.env.LEANCLOUD_APP_KEY}"
35 | }
36 |
--------------------------------------------------------------------------------
/.travis/changelog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 |
5 | TAG=$(git describe --abbrev=0)
6 | PREV_TAG=$(git describe --abbrev=0 HEAD^)
7 | PLUGIN_VERSION=$(cat build.gradle |grep pluginVersionName|cut -d \" -f 2)
8 |
9 | if [ ! -z "$TRAVIS_BUILD_ID" ]; then
10 | MD5_LINE=$(curl -s https://api.travis-ci.org/v3/job/$TRAVIS_JOB_ID/log.txt | grep -n 'exec md5sum' | cut -d : -f 1 | head -n 1)
11 | fi
12 |
13 | echo "
14 | [](https://github.com/$TRAVIS_REPO_SLUG/releases/download/$TAG/callerinfo-$TAG-release.apk)
15 | [](https://github.com/$TRAVIS_REPO_SLUG/releases/download/$TAG/CallerInfo-plugin-v$PLUGIN_VERSION-full-release.apk)
16 |
17 | ---
18 |
19 | Release files are generated and deployed by Travis-ci, check sha1 and md5 from **build log:**
20 |
21 | [https://travis-ci.org/$TRAVIS_REPO_SLUG/builds/$TRAVIS_BUILD_ID#L$MD5_LINE](https://travis-ci.org/$TRAVIS_REPO_SLUG/builds/$TRAVIS_BUILD_ID#L$MD5_LINE)
22 | "
23 |
24 | echo "## commits"
25 | git --no-pager log $PREV_TAG...$TAG \
26 | --pretty=format:" - [%s](https://github.com/$TRAVIS_REPO_SLUG/commit/%H)" \
27 | --reverse | \
28 | grep -v 'Merge pull request' | \
29 | grep -v 'build' | \
30 | grep -v 'bump'
31 |
32 | echo "
33 | ## changes
34 |
35 | - [$PREV_TAG - $TAG](https://github.com/$TRAVIS_REPO_SLUG/compare/$PREV_TAG...$TAG?diff=split)
36 | "
37 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/di/AppComponent.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.di
2 |
3 | import dagger.Component
4 | import org.xdty.callerinfo.activity.MarkActivity
5 | import org.xdty.callerinfo.application.Application
6 | import org.xdty.callerinfo.data.CallerRepository
7 | import org.xdty.callerinfo.di.modules.AppModule
8 | import org.xdty.callerinfo.fragment.SettingsFragment
9 | import org.xdty.callerinfo.model.database.DatabaseImpl
10 | import org.xdty.callerinfo.presenter.MainBottomPresenter
11 | import org.xdty.callerinfo.presenter.MainPresenter
12 | import org.xdty.callerinfo.presenter.PhoneStatePresenter
13 | import org.xdty.callerinfo.presenter.UpgradePresenter
14 | import org.xdty.callerinfo.service.FloatWindow
15 | import org.xdty.callerinfo.service.ScheduleService
16 | import org.xdty.callerinfo.utils.Alarm
17 | import org.xdty.callerinfo.utils.Contact
18 | import javax.inject.Singleton
19 |
20 | @Singleton
21 | @Component(modules = [AppModule::class])
22 | interface AppComponent {
23 | fun inject(presenter: MainPresenter)
24 | fun inject(presenter: PhoneStatePresenter)
25 | fun inject(application: Application)
26 | fun inject(service: ScheduleService)
27 | fun inject(service: FloatWindow)
28 | fun inject(alarm: Alarm)
29 | fun inject(markActivity: MarkActivity)
30 | fun inject(settingsFragment: SettingsFragment)
31 | fun inject(callerRepository: CallerRepository)
32 | fun inject(mainBottomPresenter: MainBottomPresenter)
33 | fun inject(database: DatabaseImpl)
34 | fun inject(contact: Contact)
35 | fun inject(upgradePresenter: UpgradePresenter)
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/model/db/BaseInCall.java:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.model.db;
2 |
3 | import android.text.TextUtils;
4 |
5 | import org.xdty.callerinfo.utils.Utils;
6 |
7 | import io.requery.Column;
8 | import io.requery.Entity;
9 | import io.requery.Generated;
10 | import io.requery.Key;
11 | import io.requery.Table;
12 | import io.requery.Transient;
13 |
14 | @Table(name = "IN_CALL")
15 | @Entity
16 | public abstract class BaseInCall {
17 |
18 | @Key
19 | @Generated
20 | @Column(name = "ID")
21 | int id;
22 |
23 | @Column(name = "NUMBER")
24 | String number;
25 |
26 | @Column(name = "TIME")
27 | long time;
28 |
29 | @Column(name = "RING_TIME")
30 | long ringTime;
31 |
32 | @Column(name = "DURATION")
33 | long duration;
34 |
35 | @Transient
36 | boolean isExpanded = false;
37 |
38 | public BaseInCall() {
39 | }
40 |
41 | public BaseInCall(String number, long time, long ringTime, long duration) {
42 |
43 | if (!TextUtils.isEmpty(number)) {
44 | number = number.replaceAll(" ", "");
45 | }
46 |
47 | this.number = number;
48 | this.time = time;
49 | this.ringTime = ringTime;
50 | this.duration = duration;
51 | }
52 |
53 | public boolean isExpanded() {
54 | return isExpanded;
55 | }
56 |
57 | public void setExpanded(boolean expanded) {
58 | isExpanded = expanded;
59 | }
60 |
61 | public String getReadableTime() {
62 | return Utils.Companion.readableDate(time) + " " + Utils.Companion.getTime(time);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/plugin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | defaultConfig {
6 | applicationId rootProject.ext.pluginId
7 |
8 | minSdkVersion rootProject.ext.minSdkVersion
9 | targetSdkVersion rootProject.ext.targetSdkVersion
10 |
11 | versionCode rootProject.ext.pluginVersionCode
12 | versionName rootProject.ext.pluginVersionName
13 | setProperty("archivesBaseName", "CallerInfo-plugin-v$versionName")
14 | }
15 | signingConfigs {
16 | release
17 | }
18 | buildTypes {
19 | debug {
20 | minifyEnabled true
21 | shrinkResources true
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | signingConfig signingConfigs.release
24 | }
25 | release {
26 | minifyEnabled true
27 | shrinkResources true
28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29 | signingConfig signingConfigs.release
30 | }
31 | }
32 |
33 | flavorDimensions("default")
34 |
35 | productFlavors {
36 | google {
37 | versionNameSuffix '.lite'
38 | }
39 | full {}
40 | }
41 | }
42 |
43 | dependencies {
44 | implementation fileTree(include: ['*.jar'], dir: 'libs')
45 | testImplementation 'junit:junit:4.13'
46 | implementation 'androidx.appcompat:appcompat:1.1.0'
47 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
48 | }
49 |
50 | apply from: '../signing.gradle'
--------------------------------------------------------------------------------
/app/src/main/res/raw/privacy_notice.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
19 | Privacy Notice
20 |
21 |
22 |
23 | Privacy Notice
24 |
25 |
26 |
CallerInfo is an open source project on GitHub,
27 | and does never collect your privacy data.
28 |
29 |
Notice for contact information
30 |
According to your settings, we use contact information locally to match the phone number and skip the search process, this information is never shared with others.
31 |
32 |
Notice for phone status information
33 |
We use phone status information to get the incoming or outgoing phone number and then start the search process, the incoming or outgoing phone number may be sent to Baidu or JuHe(360), but it is not linked to your own phone number or other personal information.
34 |
35 |
Notice for call log information
36 |
According to your settings, we read or write extra data to your phone's call log locally, this information is never shared with others.
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/plugin/src/main/res/raw/privacy_notice.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
19 | Privacy Notice
20 |
21 |
22 |
23 | Privacy Notice
24 |
25 |
26 |
CallerInfo is an open source project on GitHub,
27 | and does never collect your privacy data.
28 |
29 |
Notice for contact information
30 |
According to your settings, we use contact information locally to match the phone number and skip the search process, this information is never shared with others.
31 |
32 |
Notice for phone status information
33 |
We use phone status information to get the incoming or outgoing phone number and then start the search process, the incoming or outgoing phone number may be sent to Baidu or JuHe(360), but it is not linked to your own phone number or other personal information.
34 |
35 |
Notice for call log information
36 |
According to your settings, we read or write extra data to your phone's call log locally, this information is never shared with others.
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/model/database/Database.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.model.database
2 |
3 | import io.reactivex.Observable
4 | import org.xdty.callerinfo.model.db.Caller
5 | import org.xdty.callerinfo.model.db.InCall
6 | import org.xdty.callerinfo.model.db.MarkedRecord
7 | import org.xdty.phone.number.model.INumber
8 |
9 | interface Database {
10 | fun fetchInCalls(): Observable>
11 | fun fetchCallers(): Observable>
12 | fun clearAllInCalls(): Observable
13 | fun clearAllInCallSync()
14 | fun removeInCall(inCall: InCall)
15 | fun findCaller(number: String): Observable
16 | fun findCallerSync(number: String): Caller?
17 | fun removeCaller(caller: Caller)
18 | fun clearAllCallerSync(): Int
19 | fun updateCaller(caller: Caller)
20 | fun saveInCall(inCall: InCall)
21 | fun saveMarked(markedRecord: MarkedRecord)
22 | fun updateMarked(markedRecord: MarkedRecord)
23 | fun updateCaller(markedRecord: MarkedRecord)
24 | fun fetchMarkedRecords(): Observable>
25 | fun findMarkedRecord(number: String): Observable
26 | fun updateMarkedRecord(number: String)
27 | fun fetchCallersSync(): List
28 | fun fetchInCallsSync(): List
29 | fun fetchMarkedRecordsSync(): List
30 | fun addCallers(callers: List)
31 | fun addInCallers(inCalls: List)
32 | fun addMarkedRecords(markedRecords: List)
33 | fun clearAllMarkedRecordSync()
34 | fun getInCallCount(number: String): Int
35 | fun addInCallersSync(inCalls: List)
36 | fun saveMarkedRecord(number: INumber, uid: String)
37 | fun removeRecord(record: MarkedRecord)
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/contract/MainContract.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.contract
2 |
3 | import android.content.Context
4 | import org.xdty.callerinfo.model.db.Caller
5 | import org.xdty.callerinfo.model.db.InCall
6 | import org.xdty.phone.number.model.INumber
7 | import org.xdty.phone.number.model.caller.Status
8 |
9 | interface MainContract {
10 | interface View : BaseView {
11 | fun showNoCallLog(show: Boolean)
12 | fun showLoading(active: Boolean)
13 | fun showCallLogs(inCalls: List)
14 | fun showEula()
15 | fun showSearchResult(number: INumber)
16 | fun showSearching()
17 | fun showSearchFailed(isOnline: Boolean)
18 | fun attachCallerMap(callerMap: Map)
19 | val context: Context
20 | fun notifyUpdateData(status: Status)
21 | fun showUpdateData(status: Status)
22 | fun updateDataFinished(result: Boolean)
23 | fun showBottomSheet(inCall: InCall)
24 | }
25 |
26 | interface Presenter : BasePresenter {
27 | fun result(requestCode: Int, resultCode: Int)
28 | fun loadInCallList()
29 | fun loadCallerMap()
30 | fun removeInCallFromList(inCall: InCall)
31 | fun removeInCall(inCall: InCall)
32 | fun clearAll()
33 | fun search(number: String)
34 | fun checkEula()
35 | fun setEula()
36 | fun canDrawOverlays(): Boolean
37 | fun checkPermission(permission: String): Int
38 | fun clearSearch()
39 | fun dispatchUpdate(status: Status)
40 | fun getCaller(number: String): Caller
41 | fun clearCache()
42 | fun itemOnLongClicked(inCall: InCall)
43 | fun invalidateDataUpdate(isInvalidate: Boolean)
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/debug/java/org/xdty/callerinfo/application/DebugApplication.java:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.application;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.StrictMode;
5 |
6 | import com.facebook.stetho.Stetho;
7 |
8 | import io.reactivex.Completable;
9 | import io.reactivex.functions.Action;
10 | import io.reactivex.schedulers.Schedulers;
11 |
12 | public class DebugApplication extends Application {
13 | public final static String TAG = Application.class.getSimpleName();
14 |
15 | @SuppressLint("CheckResult")
16 | @Override
17 | public void onCreate() {
18 |
19 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
20 | .detectAll()
21 | .penaltyLog()
22 | .build());
23 | StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
24 | .detectLeakedSqlLiteObjects()
25 | .detectLeakedClosableObjects()
26 | .penaltyLog()
27 | //.penaltyDeath()
28 | .build());
29 |
30 | Completable.fromAction(new Action() {
31 | @Override
32 | public void run() throws Exception {
33 | Stetho.initialize(
34 | Stetho.newInitializerBuilder(DebugApplication.this)
35 | .enableDumpapp(
36 | Stetho.defaultDumperPluginsProvider(DebugApplication.this))
37 | .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(
38 | DebugApplication.this))
39 | .build());
40 | }
41 | }).subscribeOn(Schedulers.io()).subscribe();
42 |
43 | super.onCreate();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/model/db/BaseMarkedRecord.java:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.model.db;
2 |
3 | import org.xdty.phone.number.model.cloud.CloudNumber;
4 |
5 | import io.requery.Column;
6 | import io.requery.Entity;
7 | import io.requery.Generated;
8 | import io.requery.Key;
9 | import io.requery.Table;
10 |
11 | @Table(name = "MARKED_RECORD")
12 | @Entity
13 | public abstract class BaseMarkedRecord {
14 | public final static int API_ID_USER_MARKED = 8;
15 | public final static int TYPE_IGNORE = 32;
16 |
17 | @Key
18 | @Generated
19 | @Column(name = "ID")
20 | int id;
21 |
22 | @Column(name = "NUMBER", unique = true)
23 | String number;
24 |
25 | @Column(name = "UID")
26 | String uid;
27 |
28 | @Column(name = "TYPE")
29 | int type;
30 |
31 | @Column(name = "TIME")
32 | long time;
33 |
34 | @Column(name = "COUNT")
35 | int count;
36 |
37 | @Column(name = "SOURCE")
38 | int source;
39 |
40 | @Column(name = "IS_REPORTED")
41 | boolean reported;
42 |
43 | @Column(name = "TYPE_NAME")
44 | String typeName;
45 |
46 | public BaseMarkedRecord() {
47 | this.source = API_ID_USER_MARKED;
48 | this.time = System.currentTimeMillis();
49 | this.count = 0;
50 | this.reported = false;
51 | }
52 |
53 | public CloudNumber toNumber() {
54 | CloudNumber number = new CloudNumber();
55 | number.setNumber(this.number);
56 | number.setCount(this.count);
57 | number.setType(this.type);
58 | number.setFrom(this.source);
59 | number.setName(this.typeName);
60 | number.setUid(this.uid);
61 | return number;
62 | }
63 |
64 | public boolean isIgnore() {
65 | return this.type == TYPE_IGNORE;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/.travis/github_release.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'optparse'
4 | require 'octokit'
5 |
6 | options = {}
7 | OptionParser.new do |opt|
8 | opt.on('-s', '--secret SECRET', 'GitHub access token') { |o| options[:secret] = o }
9 | opt.on('-r', '--repo-slug REPO_SLUG', 'Repo slug. i.e.: apple/swift') { |o| options[:repo_slug] = o }
10 | opt.on('-c', '--changelog-file CHANGELOG_FILE', 'Changelog path') { |o| options[:changelog_file] = o }
11 | opt.on('-t', '--tag TAG', 'Tag name') { |o| options[:tag_name] = o }
12 | end.parse!
13 |
14 | raise OptionParser::MissingArgument if options[:secret].nil?
15 | raise OptionParser::MissingArgument if options[:repo_slug].nil?
16 | raise OptionParser::MissingArgument if options[:changelog_file].nil?
17 | raise OptionParser::MissingArgument if options[:tag_name].nil?
18 |
19 | client = Octokit::Client.new(:access_token => options[:secret])
20 | user = client.user
21 | user.login
22 |
23 | unless client.scopes.include? 'public_repo' or client.scopes.include? 'repo'
24 | raise Error, "Insufficient permissions. Make sure your token contains the repo or public_repo scope."
25 | end
26 |
27 | puts "Logged in as #{user.name}"
28 | puts "Deploying to repo: #{options[:repo_slug]}"
29 |
30 | tag_matched = false
31 | release_url = nil
32 | releases = client.releases(options[:repo_slug])
33 | body = File.open(options[:changelog_file], "rb").read
34 |
35 | releases.each do |release|
36 | if release.tag_name == options[:tag_name]
37 | release_url = release.rels[:self].href
38 | tag_matched = true
39 | end
40 | end
41 |
42 | # if tag has been pushed directly to git, create a github release
43 | if tag_matched == false
44 | client.create_release(options[:repo_slug], options[:tag_name], { :name => options[:tag_name], :body => body })
45 | else
46 | client.update_release(release_url, { :name => options[:tag_name], :body => body })
47 | end
48 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/activity/LicensesActivity.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.activity
2 |
3 | import android.os.Bundle
4 | import android.view.MenuItem
5 | import android.webkit.WebView
6 | import org.xdty.callerinfo.R
7 |
8 | class LicensesActivity : BaseActivity() {
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | val actionBar = supportActionBar
12 | actionBar?.setDisplayHomeAsUpEnabled(true)
13 | val webView = findViewById(R.id.webView)
14 | val action = intent.action
15 | var url = "file:///android_res/raw/licenses.html"
16 | when (action) {
17 | ACTION_LICENSE -> {
18 | }
19 | ACTION_PRIVACY -> {
20 | setTitle(R.string.privacy_notice)
21 | url = "file:///android_res/raw/privacy_notice.html"
22 | }
23 | ACTION_FEATURE -> {
24 | setTitle(R.string.feature_notice)
25 | url = "file:///android_res/raw/feature_notice.html"
26 | }
27 | }
28 | webView?.loadUrl(url)
29 | }
30 |
31 | override val layoutId: Int
32 | get() = R.layout.activity_licenses
33 |
34 | override val titleId: Int
35 | get() = R.string.license
36 |
37 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
38 | val id = item.itemId
39 | if (id == android.R.id.home) {
40 | finish()
41 | return true
42 | }
43 | return super.onOptionsItemSelected(item)
44 | }
45 |
46 | companion object {
47 | private const val ACTION_LICENSE = "org.xdty.callerinfo.action.VIEW_LICENSES"
48 | private const val ACTION_PRIVACY = "org.xdty.callerinfo.action.VIEW_PRIVACY"
49 | private const val ACTION_FEATURE = "org.xdty.callerinfo.action.VIEW_FEATURE"
50 | }
51 | }
--------------------------------------------------------------------------------
/plugin/src/main/java/org/xdty/callerinfo/plugin/LicensesActivity.java:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.plugin;
2 |
3 | import android.os.Bundle;
4 | import androidx.appcompat.app.ActionBar;
5 | import androidx.appcompat.app.AppCompatActivity;
6 | import android.view.MenuItem;
7 | import android.webkit.WebView;
8 |
9 | public class LicensesActivity extends AppCompatActivity {
10 |
11 | public final static String ACTION_LICENSE = "org.xdty.callerinfo.plugin.action.VIEW_LICENSES";
12 | public final static String ACTION_PRIVACY = "org.xdty.callerinfo.plugin.action.VIEW_PRIVACY";
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 |
18 | setContentView(R.layout.activity_licenses);
19 |
20 | ActionBar actionBar = getSupportActionBar();
21 | if (actionBar != null) {
22 | actionBar.setDisplayHomeAsUpEnabled(true);
23 | }
24 |
25 | WebView webView = (WebView) findViewById(R.id.webView);
26 |
27 | String action = getIntent().getAction();
28 | String url = "file:///android_res/raw/licenses.html";
29 |
30 | switch (action) {
31 | case ACTION_LICENSE:
32 | setTitle(R.string.license);
33 | break;
34 | case ACTION_PRIVACY:
35 | setTitle(R.string.plugin_privacy);
36 | url = "file:///android_res/raw/privacy_notice.html";
37 | break;
38 | default:
39 | break;
40 | }
41 | if (webView != null) {
42 | webView.loadUrl(url);
43 | }
44 | }
45 |
46 | @Override
47 | public boolean onOptionsItemSelected(MenuItem item) {
48 | int id = item.getItemId();
49 | if (id == android.R.id.home) {
50 | finish();
51 | return true;
52 | }
53 | return super.onOptionsItemSelected(item);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/application/Application.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.application
2 |
3 | import android.annotation.SuppressLint
4 | import android.util.Log
5 | import com.google.firebase.analytics.FirebaseAnalytics
6 | import com.tencent.bugly.crashreport.CrashReport
7 | import io.reactivex.plugins.RxJavaPlugins
8 | import org.xdty.callerinfo.di.AppComponent
9 | import org.xdty.callerinfo.di.DaggerAppComponent
10 | import org.xdty.callerinfo.di.modules.AppModule
11 | import org.xdty.callerinfo.model.setting.Setting
12 | import org.xdty.callerinfo.utils.Alarm
13 | import org.xdty.callerinfo.utils.Resource
14 | import org.xdty.callerinfo.utils.Utils
15 | import javax.inject.Inject
16 |
17 | @SuppressLint("Registered")
18 | open class Application : android.app.Application() {
19 | lateinit var analytics: FirebaseAnalytics
20 | @Inject
21 | internal lateinit var setting: Setting
22 | @Inject
23 | internal lateinit var alarm: Alarm
24 |
25 | override fun onCreate() {
26 | super.onCreate()
27 | application = this
28 | RxJavaPlugins.setErrorHandler { throwable -> Log.e(TAG, Log.getStackTraceString(throwable)) }
29 | init()
30 | }
31 |
32 | protected fun init() {
33 | appComponent = DaggerAppComponent.builder().appModule(AppModule(this)).build()
34 | appComponent.inject(this)
35 | analytics = FirebaseAnalytics.getInstance(this)
36 | analytics.logEvent(FirebaseAnalytics.Event.APP_OPEN, null)
37 | Resource.init(Utils.changeLang(this))
38 | if (!setting.isCatchCrash) {
39 | CrashReport.initCrashReport(applicationContext, "0eaf845a04", false)
40 | }
41 | setting.fix()
42 | alarm.alarm()
43 | alarm.enqueueUpgradeWork()
44 | }
45 |
46 | companion object {
47 | val TAG = Application::class.java.simpleName
48 | lateinit var application: Application
49 | lateinit var appComponent: AppComponent
50 | }
51 | }
--------------------------------------------------------------------------------
/DEVELOPMENT-CN.md:
--------------------------------------------------------------------------------
1 | [来电信息](https://github.com/xdtianyu/CallerInfo) 开发者文档
2 |
3 | 注意此文档仅供开发者使用,用于编译源码等。如果是初学者,对于文档中不明白的内容或遇到错误,请务必优先 Google 搜索。
4 |
5 | ----------------
6 |
7 | **1\. 下载最新源码**
8 |
9 | ```
10 | git clone https://github.com/xdtianyu/CallerInfo.git
11 | cd CallerInfo/
12 | git submodule update --init --recursive
13 | ```
14 |
15 | **2\. 配置编译环境**
16 |
17 | 参考 `.travis/env.sh` 文件,首先解码内置的用于公开使用的 `release.jsk` 文件
18 |
19 | ```
20 | openssl aes-256-cbc -K 12CF1B5E0D192628AA922230549EEDFD889E6CF7463933C6DABD9A1300FCA23D -iv 66813CF28D04CD129D57436B78DECBA4 -in public.tar.enc -out public.tar -d
21 | tar xvf public.tar
22 | ```
23 |
24 | 导出环境变量,注意修改 `ANDROID_HOME` 为你的 `Android SDK` 目录
25 |
26 | ```
27 | export ANDROID_HOME=/home/ty/Android/Sdk
28 |
29 | TEXT="I_AM_PUBLIC_AND_NOT_USED_FOR_RELEASE"
30 | export KEYSTORE_PASSWORD="$TEXT"
31 | export ALIAS_PASSWORD="$TEXT"
32 | export ALIAS="$TEXT"
33 | ```
34 |
35 | **3\. 运行编译**
36 |
37 | ```
38 | ./gradlew assembleDebug
39 | ```
40 |
41 | 或使用如下命令生成 `release` 版本
42 |
43 | ```
44 | ./gradlew assembleRelease
45 | ```
46 |
47 | 编译成功后,最终会在 `CallerInfo/app/build/outputs/apk` 目录生成 `CallerInfo-v2.1.5-debug.apk` 及 `CallerInfo-v2.1.5-release.apk` 文件。注意版本号可能会增加。
48 |
49 |
50 | **4\. 环境变量**
51 |
52 | 参考 `.travis/env.sh` 文件, 上文用到的 `KEYSTORE_PASSWORD` `ALIAS_PASSWORD` `ALIAS` 都是 `public.jks` 文件相关验证,`GITHUB_TOKEN` 用于作者 `GitHub Release` 自动部署。
53 |
54 | `API_KEY` 是百度号码服务 `API` 密钥,由于服务终止,已停止使用。`JUHE_API_KEY` 是聚合数据(360) 数据源 `API` 密钥。
55 |
56 | `LEANCLOUD_APP_ID` 和 `LEANCLOUD_APP_KEY` 是 `LeanCloud` 的 `API` 凭证,用于接收用户上报的号码数据。
57 |
58 | **5\. 版本配置说明**
59 |
60 | 版本配置在 `gradle.properties` 文件中。
61 |
62 | **6\. 部分文件说明**
63 |
64 | `manifest.gradle` 是用于自动写入环境变量到 `manifest` 的配置文件,可以将环境变量或 `manifest.properties` 文件内的配置导入。
65 |
66 | `signing.gradle` 类似 `manifest.gradle` 文件,用于导入环境变量或 `signing.properties` 文件内容,用于编译时证书验证配置的自动化。
67 |
68 | **7\. Android Studio 说明**
69 |
70 | `Android Studio` 导入项目后,需要修改上文提到的 `manifest.properties` `signing.properties` 文件来导入环境变量。
71 |
72 |
73 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/ty/Android/Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -dontobfuscate
19 |
20 | ##---------------Begin: proguard configuration for Gson ----------
21 | # Gson uses generic type information stored in a class file when working with fields. Proguard
22 | # removes such information by default, so configure it to keep all of it.
23 | -keepattributes Signature
24 |
25 | # Gson specific classes
26 | -keep class sun.misc.Unsafe { *; }
27 | -keep class com.google.gson.stream.** { *; }
28 | -keepattributes *Annotation*
29 | -dontwarn javax.annotation.**
30 | -dontwarn javax.inject.**
31 | -dontwarn sun.misc.Unsafe
32 |
33 | # Application classes that will be serialized/deserialized over Gson
34 | -keep class org.xdty.phone.number.model.** { *; }
35 |
36 | # OKHttp
37 | -dontwarn rx.**
38 |
39 | -dontwarn okio.**
40 |
41 | -dontwarn com.squareup.okhttp.**
42 | -keep class com.squareup.okhttp.** { *; }
43 | -keep interface com.squareup.okhttp.** { *; }
44 |
45 | -dontwarn retrofit.**
46 | -dontwarn retrofit.appengine.UrlFetchClient
47 | -keep class retrofit.** { *; }
48 | -keepclasseswithmembers class * {
49 | @retrofit.http.* ;
50 | }
51 |
52 | # Sugar
53 | -keep public class * extends com.orm.SugarApp
54 | -keep public class * extends com.orm.SugarRecord
55 |
56 | -keep class android.support.v7.** { public protected *; }
57 | -keep class android.support.v4.** { public protected *; }
58 |
59 | -keep class org.xdty.** { *; }
60 | -keep class com.facebook.stetho.** {*;}
61 |
62 | # minio
63 | -keep class io.minio.** { *; }
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/model/setting/Setting.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.model.setting
2 |
3 | import org.xdty.callerinfo.model.Status
4 |
5 | interface Setting {
6 | val isEulaSet: Boolean
7 | fun setEula()
8 | val ignoreRegex: String
9 | val isHidingOffHook: Boolean
10 | val isShowingOnOutgoing: Boolean
11 | val isIgnoreKnownContact: Boolean
12 | val isShowingContactOffline: Boolean
13 | val isAutoHangup: Boolean
14 | val isAddingCallLog: Boolean
15 | val isCatchCrash: Boolean
16 | val isForceChinese: Boolean
17 | val keywords: String
18 | val geoKeyword: String
19 | val numberKeyword: String
20 | val windowX: Int
21 | val windowY: Int
22 | fun setWindow(x: Int, y: Int)
23 | val screenWidth: Int
24 | val screenHeight: Int
25 | val statusBarHeight: Int
26 | val windowHeight: Int
27 | val defaultHeight: Int
28 | val isShowCloseAnim: Boolean
29 | val isHidingWhenTouch: Boolean
30 | val isTransBackOnly: Boolean
31 | val isEnableTextColor: Boolean
32 | val textPadding: Int
33 | val textAlignment: Int
34 | val textSize: Int
35 | val windowTransparent: Int
36 | val isDisableMove: Boolean
37 | val isAutoReportEnabled: Boolean
38 | val isMarkingEnabled: Boolean
39 | fun addPaddingMark(number: String)
40 | fun removePaddingMark(number: String)
41 | val paddingMarks: ArrayList
42 | val uid: String
43 | fun updateLastScheduleTime()
44 | fun updateLastScheduleTime(timestamp: Long)
45 | fun lastScheduleTime(): Long
46 | fun lastCheckDataUpdateTime(): Long
47 | fun updateLastCheckDataUpdateTime(timestamp: Long)
48 | var status: Status
49 | val isNotMarkContact: Boolean
50 | val isDisableOutGoingHangup: Boolean
51 | val isTemporaryDisableHangup: Boolean
52 | val repeatedCountIndex: Int
53 | fun clear()
54 | val normalColor: Int
55 | val poiColor: Int
56 | val reportColor: Int
57 | val isOnlyOffline: Boolean
58 | fun fix()
59 | fun setOutgoing(isOutgoing: Boolean)
60 | val isOutgoingPositionEnabled: Boolean
61 | val isAddingRingOnceCallLog: Boolean
62 | val isOfflineDataAutoUpgrade: Boolean
63 | }
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v1
13 |
14 | - name: Checkout submodule
15 | run: git submodule sync --recursive && git submodule update --init --recursive
16 |
17 | - name: Before build
18 | uses: xdtianyu/actions-android-ci@master
19 | #env:
20 | #ENCRYPTED_KEY: ${{ secrets.ENCRYPTED_KEY }}
21 | #ENCRYPTED_IV: ${{ secrets.ENCRYPTED_IV }}
22 | with:
23 | args: '"
24 | . .travis/env.sh;
25 | openssl aes-256-cbc -K $encrypted_75846693d905_key -iv $encrypted_75846693d905_iv -in secrets.tar.enc -out secrets.tar -d;
26 | tar xvf secrets.tar;
27 | touch local.properties
28 | "'
29 |
30 | - name: Cache gradle and sdk
31 | uses: actions/cache@v2
32 | env:
33 | cache-name: cache-gradle-and-sdk
34 | with:
35 | path: |
36 | ${{ github.workspace }}/.opt/cache/gradle/wrapper
37 | ${{ github.workspace }}/.opt/cache/gradle/caches
38 | ${{ github.workspace }}/.opt/sdk
39 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/wrapper/gradle-wrapper.properties', '**/build.gradle') }}
40 | restore-keys: |
41 | ${{ runner.os }}-build-${{ env.cache-name }}-
42 | ${{ runner.os }}-build-
43 | ${{ runner.os }}-
44 |
45 | - name: Build
46 | #env:
47 | # ENCRYPTED_KEY: ${{ secrets.ENCRYPTED_KEY }}
48 | # ENCRYPTED_IV: ${{ secrets.ENCRYPTED_IV }}
49 | # ALIAS: ${{ secrets.ALIAS }}
50 | # ALIAS_PASSWORD: ${{ secrets.ALIAS_PASSWORD }}
51 | # KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
52 | uses: xdtianyu/actions-android-ci@master
53 | with:
54 | args: '"
55 | . .travis/env.sh;
56 | . /opt/setup-android-sdk.sh;
57 | ./gradlew assembleDebug
58 | "'
59 |
60 | - name: Upload artifacts
61 | uses: actions/upload-artifact@v2
62 | with:
63 | name: artifacts
64 | path: |
65 | app/**/apk/release/*
66 | app/**/mapping/release/mapping.txt
67 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/settings/dialog/SeekBarDialog.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.settings.dialog
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import android.view.View
6 | import android.widget.SeekBar
7 | import android.widget.SeekBar.OnSeekBarChangeListener
8 | import app.minimize.com.seek_bar_compat.SeekBarCompat
9 | import org.xdty.callerinfo.R
10 |
11 | class SeekBarDialog(context: Context, sharedPreferences: SharedPreferences) :
12 | SettingsDialog(context, sharedPreferences) {
13 |
14 | lateinit var seekBar: SeekBarCompat
15 | var max: Int = 100
16 | var defaultValue: Int = 0
17 |
18 | lateinit var listener: SeekListener
19 |
20 | fun max(max: Int): SeekBarDialog {
21 | this.max = max
22 | return this
23 | }
24 |
25 | fun defaultValue(value: Int): SeekBarDialog {
26 | defaultValue = value
27 | return this
28 | }
29 |
30 | fun seek(listener: SeekListener): SeekBarDialog {
31 | this.listener = listener
32 | return this
33 | }
34 |
35 | override fun bindViews() {
36 |
37 | val layout = View.inflate(context, R.layout.dialog_seek, null)
38 | builder.setView(layout)
39 |
40 | val value = sharedPrefs.getInt(key, defaultValue)
41 | seekBar = layout.findViewById(R.id.seek_bar)
42 | seekBar.max = max
43 | seekBar.progress = value
44 | seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
45 | override fun onProgressChanged(p0: SeekBar?, progress: Int, p2: Boolean) {
46 | var fixed = progress
47 | if (fixed == 0) {
48 | fixed = 1
49 | }
50 | listener.onSeek(fixed)
51 | }
52 |
53 | override fun onStartTrackingTouch(p0: SeekBar?) {}
54 |
55 | override fun onStopTrackingTouch(p0: SeekBar?) {}
56 | })
57 | }
58 |
59 | override fun onConfirm() {
60 | val value = seekBar.progress
61 | val editor = sharedPrefs.edit()
62 | editor.putInt(key, value)
63 | editor.apply()
64 | }
65 |
66 | fun interface SeekListener {
67 | fun onSeek(progress: Int)
68 | }
69 | }
--------------------------------------------------------------------------------
/plugin/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
25 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/presenter/MainBottomPresenter.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.presenter
2 |
3 | import android.util.Log
4 | import org.xdty.callerinfo.application.Application.Companion.appComponent
5 | import org.xdty.callerinfo.contract.MainBottomContact.Presenter
6 | import org.xdty.callerinfo.contract.MainBottomContact.View
7 | import org.xdty.callerinfo.data.CallerDataSource
8 | import org.xdty.callerinfo.model.MarkType
9 | import org.xdty.callerinfo.model.database.Database
10 | import org.xdty.callerinfo.model.db.Caller
11 | import org.xdty.callerinfo.model.db.InCall
12 | import org.xdty.callerinfo.model.setting.Setting
13 | import org.xdty.callerinfo.utils.Alarm
14 | import org.xdty.callerinfo.utils.Utils
15 | import javax.inject.Inject
16 |
17 | class MainBottomPresenter(private val mView: View) : Presenter {
18 | @Inject
19 | internal lateinit var mSetting: Setting
20 | @Inject
21 | internal lateinit var mCallerDataSource: CallerDataSource
22 | @Inject
23 | internal lateinit var mDatabase: Database
24 | @Inject
25 | internal lateinit var mAlarm: Alarm
26 |
27 | private var mInCall: InCall? = null
28 | private var mCaller: Caller? = null
29 | override fun start() {
30 | if (mInCall != null) {
31 | mView.init(mInCall!!, mCaller!!)
32 | } else {
33 | Log.e(TAG, "mInCall is null")
34 | }
35 | }
36 |
37 | override fun bindData(inCall: InCall?) {
38 | mInCall = inCall
39 | mCaller = mCallerDataSource.getCallerFromCache(inCall!!.number)
40 | }
41 |
42 | override fun canMark(): Boolean {
43 | return mCaller!!.isMark || mCaller!!.canMark() && mInCall!!.duration > 0
44 | }
45 |
46 | override fun markClicked(viewId: Int) {
47 | val type: MarkType = MarkType.fromResourceId(viewId)
48 | if (type != MarkType.CUSTOM) {
49 | val typeText = Utils.typeFromId(type.toInt())
50 | mCallerDataSource.updateCaller(mInCall!!.number, type.toInt(), typeText)
51 | mView.updateMarkName(typeText)
52 | }
53 | mView.updateMark(viewId, mCaller!!)
54 | }
55 |
56 | override fun markCustom(text: String?) {
57 | mCallerDataSource.updateCaller(mInCall!!.number, MarkType.CUSTOM.toInt(),
58 | text!!)
59 | mView.updateMarkName(text)
60 | }
61 |
62 | companion object {
63 | private val TAG = MainBottomPresenter::class.java.simpleName
64 | }
65 |
66 | init {
67 | appComponent.inject(this)
68 | }
69 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/settings/dialog/RadioDialog.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.settings.dialog
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import android.view.View
6 | import android.widget.LinearLayout
7 | import android.widget.RadioButton
8 | import android.widget.RadioGroup
9 | import org.xdty.callerinfo.R
10 | import java.util.*
11 |
12 | class RadioDialog(context: Context, sharedPrefs: SharedPreferences) : SettingsDialog(context, sharedPrefs) {
13 |
14 | private var defaultValue: Int = 0
15 | private var listId: Int = 0
16 | private var offset: Int = 0
17 | lateinit var listener: CheckedListener
18 |
19 | override fun bindViews() {
20 | val layout = View.inflate(context, R.layout.dialog_radio, null)
21 | builder.setView(layout)
22 |
23 | val radioGroup: RadioGroup = layout.findViewById(R.id.radio)
24 | val layoutParams = LinearLayout.LayoutParams(
25 | LinearLayout.LayoutParams.MATCH_PARENT,
26 | LinearLayout.LayoutParams.WRAP_CONTENT)
27 |
28 |
29 | val list = Arrays.asList(*context.resources.getStringArray(listId))
30 |
31 | for (s in list) {
32 | val radioButton = RadioButton(context)
33 | radioButton.text = s
34 | radioGroup.addView(radioButton, layoutParams)
35 | }
36 |
37 | val button = radioGroup.getChildAt(
38 | sharedPrefs.getInt(key, defaultValue) - offset) as RadioButton
39 |
40 | button.isChecked = true
41 | button.setOnClickListener { dialog.dismiss() }
42 |
43 |
44 | radioGroup.setOnCheckedChangeListener { group, checkedId ->
45 | val index = group.indexOfChild(group.findViewById(checkedId))
46 | val editor = sharedPrefs.edit()
47 | editor.putInt(key, index + offset)
48 | editor.apply()
49 | dialog.dismiss()
50 |
51 | listener.onChecked(list[index])
52 | }
53 | }
54 |
55 | override fun onConfirm() {
56 |
57 | }
58 |
59 | fun defaultValue(defaultValue: Int): RadioDialog {
60 | this.defaultValue = defaultValue
61 | return this
62 | }
63 |
64 | fun offset(offset: Int): RadioDialog {
65 | this.offset = offset
66 | return this
67 | }
68 |
69 | fun listId(listId: Int): RadioDialog {
70 | this.listId = listId
71 | return this
72 | }
73 |
74 | fun check(listener: CheckedListener): RadioDialog {
75 | this.listener = listener
76 | return this
77 | }
78 |
79 | fun interface CheckedListener {
80 | fun onChecked(value: String)
81 | }
82 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/preference.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
21 |
22 |
32 |
33 |
42 |
43 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/utils/Alarm.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.utils
2 |
3 | import android.app.AlarmManager
4 | import android.app.PendingIntent
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.util.Log
8 | import androidx.lifecycle.LiveData
9 | import androidx.work.*
10 | import androidx.work.PeriodicWorkRequest.Builder
11 | import org.xdty.callerinfo.application.Application
12 | import org.xdty.callerinfo.model.setting.Setting
13 | import org.xdty.callerinfo.service.ScheduleService
14 | import org.xdty.callerinfo.worker.UpgradeWorker
15 | import java.util.concurrent.TimeUnit
16 | import javax.inject.Inject
17 |
18 | class Alarm {
19 |
20 | @Inject
21 | internal lateinit var setting: Setting
22 | @Inject
23 | internal lateinit var application: Application
24 |
25 | init {
26 | Application.appComponent.inject(this)
27 | }
28 |
29 | fun alarm() {
30 | Log.v(TAG, "alarm")
31 | if (!setting.isAutoReportEnabled && !setting.isMarkingEnabled) {
32 | Log.v(TAG, "alarm is not installed")
33 | return
34 | }
35 | val intent = Intent(application, ScheduleService::class.java)
36 | val pIntent = PendingIntent.getService(application, 0, intent, 0)
37 | val alarm = application.getSystemService(Context.ALARM_SERVICE) as AlarmManager
38 | alarm.cancel(pIntent)
39 | val now = System.currentTimeMillis()
40 | alarm.setRepeating(AlarmManager.RTC_WAKEUP, now + 5 * 1000, 60 * 60 * 1000.toLong(), pIntent)
41 | }
42 |
43 | fun enqueueUpgradeWork() {
44 | if (!setting.isOfflineDataAutoUpgrade) {
45 | Log.d(TAG, "Offline data auto upgrade is not enabled.")
46 | return
47 | }
48 | val builder = Builder(UpgradeWorker::class.java, 6, TimeUnit.HOURS)
49 | val constraints: Constraints = Constraints.Builder()
50 | // .setRequiredNetworkType(NetworkType.CONNECTED)
51 | // .setRequiresBatteryNotLow(true)
52 | // .setRequiresStorageNotLow(true)
53 | .build()
54 | builder.setConstraints(constraints)
55 | val request = builder.build()
56 | WorkManager.getInstance().enqueueUniquePeriodicWork(UpgradeWorker::class.java.simpleName,
57 | ExistingPeriodicWorkPolicy.KEEP, request)
58 | }
59 |
60 | fun cancelUpgradeWork() {
61 | WorkManager.getInstance().cancelUniqueWork(UpgradeWorker::class.java.simpleName)
62 | }
63 |
64 | fun runUpgradeWorkOnce(): LiveData {
65 | val request: OneTimeWorkRequest = OneTimeWorkRequest.Builder(UpgradeWorker::class.java).build()
66 | val workManager = WorkManager.getInstance()
67 | workManager.enqueue(request)
68 | return workManager.getWorkInfoByIdLiveData(request.id)
69 | }
70 |
71 | companion object {
72 | private val TAG = Alarm::class.java.simpleName
73 | }
74 |
75 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @color/tomato
5 | - @color/red_light
6 | - @color/tangerine
7 | - @color/orange_dark
8 | - @color/banana
9 | - @color/basil
10 | - @color/sage
11 | - @color/blue_light
12 | - @color/peacock
13 | - @color/blueberry
14 | - @color/lavender
15 | - @color/grape
16 | - @color/flamingo
17 | - @color/graphite
18 |
19 |
20 | - @string/api_juhe
21 | - @string/api_sogou
22 |
23 |
24 | - @string/align_left
25 | - @string/align_center
26 | - @string/align_right
27 |
28 |
29 | - @string/harassment
30 | - @string/fraud
31 | - @string/advertising
32 | - @string/express_delivery
33 | - @string/restaurant_deliver
34 |
35 |
36 |
37 | - @string/type_harassment
38 | - @string/type_fraud
39 | - @string/type_advertising
40 | - @string/type_express_delivery
41 | - @string/type_restaurant_deliver
42 |
43 |
44 | - @string/repeated_incoming_twice
45 | - @string/repeated_incoming_third
46 | - @string/repeated_incoming_fourth
47 | - @string/repeated_incoming_fifth
48 |
49 |
50 | - -1000
51 | - -200
52 | - -150
53 | - -100
54 | - -50
55 | - -2
56 | - -1
57 | - 0
58 | - 1
59 | - 2
60 | - 8
61 | - 1000
62 |
63 |
64 | - @string/source_special
65 | - @string/source_common
66 | - @string/source_caller
67 | - @string/source_marked
68 | - @string/source_mvno
69 | - @string/source_offline
70 | - @string/source_google
71 | - @string/source_baidu
72 | - @string/source_juhe
73 | - @string/source_soguo
74 | - @string/source_cloud
75 | - @string/source_custom
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/model/CallRecord.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.model
2 |
3 | class CallRecord {
4 | private var ring: Long = -1
5 | var hook: Long = -1
6 | private set
7 | private var idle: Long = -1
8 | private var ringDuration: Long = -1
9 | private var callDuration: Long = -1
10 | var logNumber: String = ""
11 | var logName: String = ""
12 | var logGeo: String = ""
13 |
14 | val isIncoming: Boolean
15 | get() = ring != -1L
16 |
17 | val isValid: Boolean
18 | get() = logNumber.isNotEmpty()
19 |
20 | val isNameValid: Boolean
21 | get() = logName.isNotEmpty()
22 |
23 | val isGeoValid: Boolean
24 | get() = logGeo.isNotEmpty()
25 |
26 | val isActive: Boolean
27 | get() = ring != -1L || hook != -1L || idle != -1L
28 |
29 | val isAnswered: Boolean
30 | get() = isIncoming && callDuration > 0
31 |
32 | fun setLogName(name: String, append: Boolean) {
33 | if (append) {
34 | appendName(name)
35 | } else {
36 | logName = name
37 | }
38 | }
39 |
40 | fun ring() {
41 | ring = System.currentTimeMillis()
42 | }
43 |
44 | fun hook() {
45 | hook = System.currentTimeMillis()
46 | }
47 |
48 | fun idle() {
49 | idle = System.currentTimeMillis()
50 | if (isIncoming) {
51 | if (hook == -1L) { // missed or hangup incoming call
52 | ringDuration = idle - ring
53 | callDuration = 0
54 | } else { // answered incoming call
55 | ringDuration = hook - ring
56 | callDuration = idle - hook
57 | }
58 | } else { // outgoing call
59 | ringDuration = 0
60 | callDuration = idle - hook
61 | }
62 | }
63 |
64 | fun ringDuration(): Long {
65 | return ringDuration
66 | }
67 |
68 | fun callDuration(): Long {
69 | return callDuration
70 | }
71 |
72 | fun reset() {
73 | ring = -1
74 | hook = -1
75 | idle = -1
76 | ringDuration = -1
77 | callDuration = -1
78 | logNumber = ""
79 | logGeo = ""
80 | logName = ""
81 | }
82 |
83 | fun time(): Long {
84 | return if (ring != -1L) ring else hook
85 | }
86 |
87 | private fun appendName(s: String) {
88 | logName += " $s"
89 | }
90 |
91 | fun matchName(keyword: String?): Boolean {
92 | return logName.contains(keyword!!)
93 | }
94 |
95 | fun matchGeo(keyword: String?): Boolean {
96 | return logGeo.contains(keyword!!)
97 | }
98 |
99 | fun matchNumber(keyword: String?): Boolean {
100 | return logNumber.startsWith(keyword!!)
101 | }
102 |
103 | fun isEqual(number: String?): Boolean {
104 | return (number == null || logNumber == number)
105 | }
106 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/xdty/callerinfo/utils/Contact.kt:
--------------------------------------------------------------------------------
1 | package org.xdty.callerinfo.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.provider.ContactsContract
5 | import io.reactivex.Observable
6 | import io.reactivex.android.schedulers.AndroidSchedulers
7 | import io.reactivex.schedulers.Schedulers
8 | import org.xdty.callerinfo.application.Application.Companion.appComponent
9 | import org.xdty.callerinfo.application.Application.Companion.application
10 | import org.xdty.callerinfo.model.permission.Permission
11 | import javax.inject.Inject
12 |
13 | @SuppressLint("CheckResult")
14 | class Contact private constructor() {
15 | @Inject
16 | internal lateinit var mPermission: Permission
17 |
18 | private val mContactMap: MutableMap = HashMap()
19 | private var lastUpdateTime: Long = 0
20 | fun isExist(number: String?): Boolean {
21 | loadContactCache()
22 | return mContactMap.containsKey(number)
23 | }
24 |
25 | fun getName(number: String): String {
26 | return if (mContactMap.containsKey(number)) {
27 | mContactMap[number]!!
28 | } else ""
29 | }
30 |
31 | private fun loadContactCache() {
32 | if (mPermission.canReadContact() && System.currentTimeMillis() - lastUpdateTime
33 | > Constants.CONTACT_CACHE_INTERVAL) {
34 | loadContactMap().subscribe { map ->
35 | mContactMap.clear()
36 | mContactMap.putAll(map!!)
37 | lastUpdateTime = System.currentTimeMillis()
38 | }
39 | }
40 | }
41 |
42 | private fun loadContactMap(): Observable