-
-
-
\ No newline at end of file
diff --git a/.idea/markdown-navigator.xml b/.idea/markdown-navigator.xml
deleted file mode 100644
index 4e63b3e..0000000
--- a/.idea/markdown-navigator.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/markdown-navigator/profiles_settings.xml b/.idea/markdown-navigator/profiles_settings.xml
deleted file mode 100644
index db06266..0000000
--- a/.idea/markdown-navigator/profiles_settings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 4896e0c..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460..0000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..31b0c63
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2021 aJIEw
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index a39f5aa..86798a3 100644
--- a/README.md
+++ b/README.md
@@ -18,4 +18,4 @@ This project shows you how to make your app as the default phone call app and ma

-原文链接:[安卓代替系统默认电话应用 (Android 6.0+) 与电话状态监听](https://ajiew.github.io/2018-08-11-android-replace-default-phone-app-and-listen-phone-calls/)
+原文链接:[安卓代替系统默认电话应用 (Android 6.0+) 与电话状态监听](https://ajiew.github.io/android-phone-call-app-and-listen-phone-calls/)
diff --git a/app/build.gradle b/app/build.gradle
index 8198f6d..3973840 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,21 +1,27 @@
-apply plugin: 'com.android.application'
+plugins {
+ id "com.android.application"
+}
android {
- compileSdkVersion 28
+ namespace 'com.ajiew.phonecallapp'
+ compileSdk 35
+
defaultConfig {
applicationId "com.ajiew.phonecallapp"
- minSdkVersion 16
- targetSdkVersion 28
+ minSdk 21
+ targetSdk 35
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
+
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@@ -24,10 +30,11 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
- implementation 'com.android.support:appcompat-v7:28.0.0'
- implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'androidx.activity:activity:1.10.1'
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.2'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
diff --git a/app/src/androidTest/java/com/ajiew/phonecallapp/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/ajiew/phonecallapp/ExampleInstrumentedTest.java
index 38e9f4f..ecf7a93 100644
--- a/app/src/androidTest/java/com/ajiew/phonecallapp/ExampleInstrumentedTest.java
+++ b/app/src/androidTest/java/com/ajiew/phonecallapp/ExampleInstrumentedTest.java
@@ -1,8 +1,8 @@
package com.ajiew.phonecallapp;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f200eaf..ea7f20a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,12 +1,21 @@
+ xmlns:tools="http://schemas.android.com/tools">
+
+
+
+
+
+
+
+
-
+
@@ -24,7 +36,9 @@
+ android:launchMode="singleInstance"
+ android:exported="true"
+ tools:targetApi="23">
@@ -47,7 +61,9 @@
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:exported="false"
+ tools:targetApi="23">
@@ -66,4 +82,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/com/ajiew/phonecallapp/ActivityStack.java b/app/src/main/java/com/ajiew/phonecallapp/ActivityStack.java
index 019acf0..00a822d 100644
--- a/app/src/main/java/com/ajiew/phonecallapp/ActivityStack.java
+++ b/app/src/main/java/com/ajiew/phonecallapp/ActivityStack.java
@@ -3,6 +3,7 @@
import android.app.Activity;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
@@ -10,14 +11,16 @@ public class ActivityStack {
private static final ActivityStack INSTANCE = new ActivityStack();
- private List activities = new ArrayList<>();
+ private final List activities = new ArrayList<>();
public static ActivityStack getInstance() {
return INSTANCE;
}
public void addActivity(Activity activity) {
- activities.add(activity);
+ if (activity != null) {
+ activities.add(activity);
+ }
}
public Activity getTopActivity() {
@@ -41,18 +44,27 @@ public void finishActivity(Activity activity) {
}
public void finishActivity(Class activityClass) {
- for (Activity activity : activities) {
- if (activity.getClass().equals(activityClass)) {
- finishActivity(activity);
+ if (activityClass == null) {
+ return;
+ }
+
+ Iterator iterator = activities.iterator();
+ while (iterator.hasNext()) {
+ Activity activity = iterator.next();
+ if (activity != null && activity.getClass().equals(activityClass)) {
+ iterator.remove();
+ activity.finish();
}
}
}
public void finishAllActivity() {
- if (!activities.isEmpty()) {
- for (Activity activity : activities) {
+ Iterator iterator = activities.iterator();
+ while (iterator.hasNext()) {
+ Activity activity = iterator.next();
+ iterator.remove();
+ if (activity != null) {
activity.finish();
- activities.remove(activity);
}
}
}
diff --git a/app/src/main/java/com/ajiew/phonecallapp/MainActivity.java b/app/src/main/java/com/ajiew/phonecallapp/MainActivity.java
index 066f71c..681c920 100644
--- a/app/src/main/java/com/ajiew/phonecallapp/MainActivity.java
+++ b/app/src/main/java/com/ajiew/phonecallapp/MainActivity.java
@@ -1,21 +1,31 @@
package com.ajiew.phonecallapp;
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
import android.app.ActivityManager;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AlertDialog;
-import android.support.v7.app.AppCompatActivity;
import android.telecom.TelecomManager;
import android.view.WindowManager;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.Toast;
+import androidx.activity.EdgeToEdge;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
+
import com.ajiew.phonecallapp.listenphonecall.CallListenerService;
import java.lang.reflect.Field;
@@ -23,14 +33,37 @@
public class MainActivity extends AppCompatActivity {
+ @SuppressLint("UseSwitchCompatOrMaterialCode")
private Switch switchPhoneCall;
+ @SuppressLint("UseSwitchCompatOrMaterialCode")
private Switch switchListenCall;
private CompoundButton.OnCheckedChangeListener switchCallCheckChangeListener;
+ private final ActivityResultLauncher dialerRequestLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(), result -> {
+ if (result.getResultCode() == Activity.RESULT_OK) {
+ Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用",
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ private final ActivityResultLauncher phoneStatePermissionLauncher = registerForActivityResult(
+ new ActivityResultContracts.RequestPermission(), granted -> {
+ if (granted) {
+ if (switchListenCall != null && switchListenCall.isChecked()) {
+ startCallListenerService();
+ }
+ } else {
+ Toast.makeText(this, "缺少获取电话状态权限", Toast.LENGTH_SHORT).show();
+ setSwitchListenCallChecked(false);
+ }
+ });
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
+ EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
@@ -45,16 +78,29 @@ private void initView() {
// 发起将本应用设为默认电话应用的请求,仅支持 Android M 及以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (switchPhoneCall.isChecked()) {
- Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
- intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,
- getPackageName());
- startActivity(intent);
+ // Android 10 之后需要通过 RoleManager 修改默认电话应用
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
+ RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);
+ if (roleManager == null
+ || !roleManager.isRoleAvailable(RoleManager.ROLE_DIALER)) {
+ Toast.makeText(this, "当前设备不支持设置默认电话应用", Toast.LENGTH_SHORT).show();
+ switchPhoneCall.setChecked(false);
+ return;
+ }
+ Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
+ dialerRequestLauncher.launch(intent);
+ } else {
+ Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
+ intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, getPackageName());
+ startActivity(intent);
+ }
} else {
// 取消时跳转到默认设置页面
startActivity(new Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS"));
}
} else {
- Toast.makeText(MainActivity.this, "Android 6.0 以上才支持修改默认电话应用!", Toast.LENGTH_LONG).show();
+ Toast.makeText(MainActivity.this, "Android 6.0 以上才支持修改默认电话应用!", Toast.LENGTH_LONG)
+ .show();
switchPhoneCall.setChecked(false);
}
@@ -63,8 +109,7 @@ private void initView() {
// 检查是否开启了权限
switchCallCheckChangeListener = (buttonView, isChecked) -> {
if (isChecked && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
- && !Settings.canDrawOverlays(MainActivity.this)
- ) {
+ && !Settings.canDrawOverlays(MainActivity.this)) {
// 请求 悬浮框 权限
askForDrawOverlay();
@@ -75,18 +120,43 @@ private void initView() {
return;
}
- Intent callListener = new Intent(MainActivity.this, CallListenerService.class);
+ boolean granted = ContextCompat.checkSelfPermission(this,
+ Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED;
+ if (isChecked && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !granted) {
+ phoneStatePermissionLauncher.launch(Manifest.permission.READ_PHONE_STATE);
+ return;
+ }
+
if (isChecked) {
- startService(callListener);
- Toast.makeText(this, "电话监听服务已开启", Toast.LENGTH_SHORT).show();
+ startCallListenerService();
} else {
- stopService(callListener);
- Toast.makeText(this, "电话监听服务已关闭", Toast.LENGTH_SHORT).show();
+ stopCallListenerService();
}
};
switchListenCall.setOnCheckedChangeListener(switchCallCheckChangeListener);
}
+ private void startCallListenerService() {
+ Intent callListener = new Intent(MainActivity.this, CallListenerService.class);
+ startService(callListener);
+ Toast.makeText(this, "电话监听服务已开启", Toast.LENGTH_SHORT).show();
+ }
+
+ private void stopCallListenerService() {
+ Intent callListener = new Intent(MainActivity.this, CallListenerService.class);
+ stopService(callListener);
+ Toast.makeText(this, "电话监听服务已关闭", Toast.LENGTH_SHORT).show();
+ }
+
+ private void setSwitchListenCallChecked(boolean checked) {
+ if (switchListenCall == null) {
+ return;
+ }
+ switchListenCall.setOnCheckedChangeListener(null);
+ switchListenCall.setChecked(checked);
+ switchListenCall.setOnCheckedChangeListener(switchCallCheckChangeListener);
+ }
+
private void askForDrawOverlay() {
AlertDialog alertDialog = new AlertDialog.Builder(this)
.setTitle("允许显示悬浮框")
@@ -98,11 +168,12 @@ private void askForDrawOverlay() {
.setNegativeButton("稍后再说", (dialog, which) -> dialog.dismiss())
.create();
- //noinspection ConstantConditions
- alertDialog.getWindow().setFlags(
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
alertDialog.show();
+ if (alertDialog.getWindow() != null) {
+ alertDialog.getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+ }
}
/**
@@ -151,8 +222,10 @@ public boolean isServiceRunning(Class> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
if (manager == null) return false;
- for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
- if (serviceClass.getName().equals(service.service.getClassName())) {
+ for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(
+ Integer.MAX_VALUE)) {
+ if (service != null && service.service != null
+ && serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
diff --git a/app/src/main/java/com/ajiew/phonecallapp/listenphonecall/CallListenerService.java b/app/src/main/java/com/ajiew/phonecallapp/listenphonecall/CallListenerService.java
index 6e97ee8..8da5db6 100644
--- a/app/src/main/java/com/ajiew/phonecallapp/listenphonecall/CallListenerService.java
+++ b/app/src/main/java/com/ajiew/phonecallapp/listenphonecall/CallListenerService.java
@@ -7,10 +7,7 @@
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
-import android.support.annotation.Nullable;
-import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -20,6 +17,9 @@
import android.widget.FrameLayout;
import android.widget.TextView;
+import androidx.core.content.ContextCompat;
+import androidx.annotation.Nullable;
+
import com.ajiew.phonecallapp.MainActivity;
import com.ajiew.phonecallapp.R;
@@ -33,8 +33,7 @@ public class CallListenerService extends Service {
private WindowManager windowManager;
private WindowManager.LayoutParams params;
- private PhoneStateListener phoneStateListener;
- private TelephonyManager telephonyManager;
+ private PhoneStateListenerCompat phoneStateListenerCompat;
private String callNumber;
private boolean hasShown;
@@ -44,9 +43,11 @@ public class CallListenerService extends Service {
public void onCreate() {
super.onCreate();
- initPhoneStateListener();
-
- initPhoneCallView();
+ if (initPhoneCallView()) {
+ initPhoneStateListener();
+ } else {
+ stopSelf();
+ }
}
@Nullable
@@ -59,16 +60,17 @@ public IBinder onBind(Intent intent) {
* 初始化来电状态监听器
*/
private void initPhoneStateListener() {
- phoneStateListener = new PhoneStateListener() {
+ phoneStateListenerCompat = new PhoneStateListenerCompat() {
@Override
- public void onCallStateChanged(int state, String incomingNumber) {
- super.onCallStateChanged(state, incomingNumber);
-
- callNumber = incomingNumber;
+ public void onCallStateChanged(int state, String phoneNumber) {
+ if (phoneNumber != null && !phoneNumber.isEmpty()) {
+ callNumber = phoneNumber;
+ }
switch (state) {
case TelephonyManager.CALL_STATE_IDLE: // 待机,即无电话时,挂断时触发
dismiss();
+ callNumber = null;
break;
case TelephonyManager.CALL_STATE_RINGING: // 响铃,来电时触发
@@ -88,24 +90,19 @@ public void onCallStateChanged(int state, String incomingNumber) {
}
}
};
-
- // 设置来电监听器
- telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
- if (telephonyManager != null) {
- telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- }
-
+ phoneStateListenerCompat.startListening(this);
}
- private void initPhoneCallView() {
+ private boolean initPhoneCallView() {
windowManager = (WindowManager) getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE);
- int width = windowManager.getDefaultDisplay().getWidth();
- int height = windowManager.getDefaultDisplay().getHeight();
+ if (windowManager == null) {
+ return false;
+ }
params = new WindowManager.LayoutParams();
params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
- params.width = width;
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
@@ -145,22 +142,30 @@ public boolean dispatchKeyEvent(KeyEvent event) {
}
};
- phoneCallView = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE))
- .inflate(R.layout.view_phone_call, interceptorLayout);
+ LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ if (layoutInflater == null) {
+ return false;
+ }
+
+ phoneCallView = layoutInflater.inflate(R.layout.view_phone_call, interceptorLayout);
tvCallNumber = phoneCallView.findViewById(R.id.tv_call_number);
btnOpenApp = phoneCallView.findViewById(R.id.btn_open_app);
+ if (tvCallNumber == null || btnOpenApp == null) {
+ return false;
+ }
btnOpenApp.setOnClickListener(v -> {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
CallListenerService.this.startActivity(intent);
});
+ return true;
}
/**
* 显示顶级弹框展示通话信息
*/
private void show() {
- if (!hasShown) {
+ if (!hasShown && windowManager != null && phoneCallView != null && params != null) {
windowManager.addView(phoneCallView, params);
hasShown = true;
}
@@ -170,7 +175,7 @@ private void show() {
* 取消显示
*/
private void dismiss() {
- if (hasShown) {
+ if (hasShown && windowManager != null && phoneCallView != null) {
windowManager.removeView(phoneCallView);
isCallingIn = false;
hasShown = false;
@@ -178,15 +183,20 @@ private void dismiss() {
}
private void updateUI() {
- tvCallNumber.setText(formatPhoneNumber(callNumber));
+ if (callNumber != null && !callNumber.isEmpty()) {
+ tvCallNumber.setText(formatPhoneNumber(callNumber));
+ }
int callTypeDrawable = isCallingIn ? R.drawable.ic_phone_call_in : R.drawable.ic_phone_call_out;
tvCallNumber.setCompoundDrawablesWithIntrinsicBounds(null, null,
- getResources().getDrawable(callTypeDrawable), null);
+ ContextCompat.getDrawable(this, callTypeDrawable), null);
}
public static String formatPhoneNumber(String phoneNum) {
- if (!TextUtils.isEmpty(phoneNum) && phoneNum.length() == 11) {
+ if (phoneNum == null || phoneNum.isEmpty()) {
+ return "未知号码";
+ }
+ if (phoneNum.length() == 11) {
return phoneNum.substring(0, 3) + "-"
+ phoneNum.substring(3, 7) + "-"
+ phoneNum.substring(7);
@@ -198,6 +208,8 @@ public static String formatPhoneNumber(String phoneNum) {
public void onDestroy() {
super.onDestroy();
- telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
+ if (phoneStateListenerCompat != null) {
+ phoneStateListenerCompat.stopListening(this);
+ }
}
}
diff --git a/app/src/main/java/com/ajiew/phonecallapp/listenphonecall/PhoneStateListenerCompat.java b/app/src/main/java/com/ajiew/phonecallapp/listenphonecall/PhoneStateListenerCompat.java
new file mode 100644
index 0000000..e6c4fca
--- /dev/null
+++ b/app/src/main/java/com/ajiew/phonecallapp/listenphonecall/PhoneStateListenerCompat.java
@@ -0,0 +1,83 @@
+package com.ajiew.phonecallapp.listenphonecall;
+
+import android.content.Context;
+import android.os.Build;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * 电话状态监听器,API 31+ 使用 {@link android.telephony.TelephonyCallback.CallStateListener}。
+ *
+ * @author aJIEw
+ * Created on: 2025/5/6 16:45
+ */
+public abstract class PhoneStateListenerCompat {
+ private PhoneStateListener phoneStateListener;
+ private CallStateTelephonyCallback callStateListener;
+
+ @SuppressWarnings("deprecation")
+ public void startListening(Context context) {
+ TelephonyManager tm = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (tm == null) {
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ callStateListener = new CallStateTelephonyCallback() {
+ @Override
+ public void onCallStateChanged(int state) {
+ PhoneStateListenerCompat.this.onCallStateChanged(state, null);
+ }
+ };
+ tm.registerTelephonyCallback(context.getMainExecutor(), callStateListener);
+ } else {
+ phoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onCallStateChanged(int state, String phoneNumber) {
+ PhoneStateListenerCompat.this.onCallStateChanged(state, phoneNumber);
+ }
+ };
+ startLegacyListening(tm, phoneStateListener);
+ }
+ }
+
+ public void stopListening(Context context) {
+ TelephonyManager tm = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (tm == null) {
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (callStateListener != null) {
+ tm.unregisterTelephonyCallback(callStateListener);
+ callStateListener = null;
+ }
+ } else {
+ if (phoneStateListener != null) {
+ stopLegacyListening(tm, phoneStateListener);
+ phoneStateListener = null;
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void startLegacyListening(TelephonyManager tm, PhoneStateListener listener) {
+ tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
+ }
+
+ @SuppressWarnings("deprecation")
+ private void stopLegacyListening(TelephonyManager tm, PhoneStateListener listener) {
+ tm.listen(listener, PhoneStateListener.LISTEN_NONE);
+ }
+
+ public abstract void onCallStateChanged(int state, String phoneNumber);
+}
+
+@RequiresApi(api = Build.VERSION_CODES.S)
+abstract class CallStateTelephonyCallback extends android.telephony.TelephonyCallback
+ implements android.telephony.TelephonyCallback.CallStateListener {
+}
diff --git a/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallActivity.java b/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallActivity.java
index 967a387..103b1ea 100644
--- a/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallActivity.java
+++ b/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallActivity.java
@@ -1,24 +1,26 @@
package com.ajiew.phonecallapp.phonecallui;
+import static com.ajiew.phonecallapp.listenphonecall.CallListenerService.formatPhoneNumber;
+
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
-import android.support.annotation.RequiresApi;
-import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
+import androidx.activity.EdgeToEdge;
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.app.AppCompatActivity;
+
import com.ajiew.phonecallapp.ActivityStack;
import com.ajiew.phonecallapp.R;
import java.util.Timer;
import java.util.TimerTask;
-import static com.ajiew.phonecallapp.listenphonecall.CallListenerService.formatPhoneNumber;
-
/**
* 提供接打电话的界面,仅支持 Android M (6.0, API 23) 及以上的系统
@@ -42,7 +44,7 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick
private int callingTime;
public static void actionStart(Context context, String phoneNumber,
- PhoneCallService.CallType callType) {
+ PhoneCallService.CallType callType) {
Intent intent = new Intent(context, PhoneCallActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_MIME_TYPES, callType);
@@ -52,6 +54,7 @@ public static void actionStart(Context context, String phoneNumber,
@Override
protected void onCreate(Bundle savedInstanceState) {
+ EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_phone_call);
diff --git a/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallManager.java b/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallManager.java
index f551c00..f1a8df8 100644
--- a/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallManager.java
+++ b/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallManager.java
@@ -3,10 +3,11 @@
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
-import android.support.annotation.RequiresApi;
import android.telecom.Call;
import android.telecom.VideoProfile;
+import androidx.annotation.RequiresApi;
+
@RequiresApi(api = Build.VERSION_CODES.M)
public class PhoneCallManager {
@@ -53,7 +54,7 @@ public void openSpeaker() {
/**
* 销毁资源
- * */
+ */
public void destroy() {
call = null;
context = null;
diff --git a/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallService.java b/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallService.java
index d057c7a..5dde231 100644
--- a/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallService.java
+++ b/app/src/main/java/com/ajiew/phonecallapp/phonecallui/PhoneCallService.java
@@ -1,12 +1,15 @@
package com.ajiew.phonecallapp.phonecallui;
+import android.net.Uri;
import android.os.Build;
-import android.support.annotation.RequiresApi;
import android.telecom.Call;
import android.telecom.InCallService;
+import androidx.annotation.RequiresApi;
+
import com.ajiew.phonecallapp.ActivityStack;
+
/**
* 监听电话通信状态的服务,实现该类的同时必须提供电话管理的 UI
*
@@ -17,7 +20,7 @@
@RequiresApi(api = Build.VERSION_CODES.M)
public class PhoneCallService extends InCallService {
- private Call.Callback callback = new Call.Callback() {
+ private final Call.Callback callback = new Call.Callback() {
@Override
public void onStateChanged(Call call, int state) {
super.onStateChanged(call, state);
@@ -40,6 +43,9 @@ public void onStateChanged(Call call, int state) {
@Override
public void onCallAdded(Call call) {
super.onCallAdded(call);
+ if (call == null) {
+ return;
+ }
call.registerCallback(callback);
PhoneCallManager.call = call;
@@ -53,8 +59,7 @@ public void onCallAdded(Call call) {
}
if (callType != null) {
- Call.Details details = call.getDetails();
- String phoneNumber = details.getHandle().getSchemeSpecificPart();
+ String phoneNumber = getPhoneNumber(call);
PhoneCallActivity.actionStart(this, phoneNumber, callType);
}
}
@@ -62,11 +67,29 @@ public void onCallAdded(Call call) {
@Override
public void onCallRemoved(Call call) {
super.onCallRemoved(call);
+ if (call == null) {
+ PhoneCallManager.call = null;
+ return;
+ }
call.unregisterCallback(callback);
PhoneCallManager.call = null;
}
+ private String getPhoneNumber(Call call) {
+ Call.Details details = call.getDetails();
+ if (details == null) {
+ return null;
+ }
+
+ Uri handle = details.getHandle();
+ if (handle == null) {
+ return null;
+ }
+
+ return handle.getSchemeSpecificPart();
+ }
+
public enum CallType {
CALL_IN,
CALL_OUT,
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 3eab884..52e24cb 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -26,7 +26,7 @@
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="48dp"
android:text="设为默认电话应用" />
-
@@ -48,6 +51,8 @@
android:layout_centerHorizontal="true"
android:layout_marginTop="24dp"
android:text="通话中:01:33"
+ android:textColor="@android:color/black"
+ android:textSize="18sp"
android:visibility="gone"
tools:visibility="visible" />
diff --git a/app/src/main/res/layout/view_phone_call.xml b/app/src/main/res/layout/view_phone_call.xml
index 81592a9..5036c76 100644
--- a/app/src/main/res/layout/view_phone_call.xml
+++ b/app/src/main/res/layout/view_phone_call.xml
@@ -14,7 +14,7 @@
android:layout_marginTop="30dp"
android:gravity="center"
android:textAlignment="center"
- android:textColor="@color/colorAccent"
+ android:textColor="@android:color/white"
android:textSize="18sp"
tools:text="133-8892-9579" />
@@ -22,6 +22,7 @@
android:id="@+id/btn_open_app"
style="@style/Base.Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="match_parent"
+ android:textColor="@android:color/white"
android:layout_height="wrap_content"
android:layout_below="@id/tv_call_number"
android:gravity="center"
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 4f2b693..0eb88fe 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -6,8 +6,6 @@
@color/colorPrimary@color/colorPrimaryDark@color/colorAccent
- true
- @null
diff --git a/app/src/test/java/com/ajiew/phonecallapp/ActivityStackTest.java b/app/src/test/java/com/ajiew/phonecallapp/ActivityStackTest.java
new file mode 100644
index 0000000..edd0e13
--- /dev/null
+++ b/app/src/test/java/com/ajiew/phonecallapp/ActivityStackTest.java
@@ -0,0 +1,41 @@
+package com.ajiew.phonecallapp;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+
+import org.junit.After;
+import org.junit.Test;
+
+public class ActivityStackTest {
+
+ @After
+ public void tearDown() {
+ ActivityStack.getInstance().finishAllActivity();
+ }
+
+ @Test
+ public void finishActivityByClass_removesMatchingActivitiesWithoutCrash() {
+ TestActivity firstActivity = new TestActivity();
+ TestActivity secondActivity = new TestActivity();
+
+ ActivityStack.getInstance().addActivity(firstActivity);
+ ActivityStack.getInstance().addActivity(secondActivity);
+
+ ActivityStack.getInstance().finishActivity(TestActivity.class);
+
+ assertTrue(firstActivity.finished);
+ assertTrue(secondActivity.finished);
+ assertNull(ActivityStack.getInstance().getTopActivity());
+ }
+
+ public static class TestActivity extends Activity {
+ boolean finished;
+
+ @Override
+ public void finish() {
+ finished = true;
+ }
+ }
+}
diff --git a/app/src/test/java/com/ajiew/phonecallapp/listenphonecall/CallListenerServiceTest.java b/app/src/test/java/com/ajiew/phonecallapp/listenphonecall/CallListenerServiceTest.java
new file mode 100644
index 0000000..4331231
--- /dev/null
+++ b/app/src/test/java/com/ajiew/phonecallapp/listenphonecall/CallListenerServiceTest.java
@@ -0,0 +1,28 @@
+package com.ajiew.phonecallapp.listenphonecall;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class CallListenerServiceTest {
+
+ @Test
+ public void formatPhoneNumber_returnsUnknownWhenNull() {
+ assertEquals("未知号码", CallListenerService.formatPhoneNumber(null));
+ }
+
+ @Test
+ public void formatPhoneNumber_returnsUnknownWhenEmpty() {
+ assertEquals("未知号码", CallListenerService.formatPhoneNumber(""));
+ }
+
+ @Test
+ public void formatPhoneNumber_formatsElevenDigitNumber() {
+ assertEquals("133-8892-9579", CallListenerService.formatPhoneNumber("13388929579"));
+ }
+
+ @Test
+ public void formatPhoneNumber_keepsNonElevenDigitNumber() {
+ assertEquals("10086", CallListenerService.formatPhoneNumber("10086"));
+ }
+}
diff --git a/build.gradle b/build.gradle
index 077cb2f..c6aaff6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,27 +1,4 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
-
- repositories {
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
-
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
+plugins {
+ id "com.android.application" version '8.9.2' apply false
}
diff --git a/gradle.properties b/gradle.properties
index 743d692..d54129d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,8 +6,13 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
+org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
+org.gradle.daemon=true
+org.gradle.configureondemand=true
+org.gradle.parallel=true
+org.gradle.caching=true
+
+android.enableJetifier=true
+android.useAndroidX=true
+android.nonTransitiveRClass=false
+android.nonFinalResIds=false
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7a3265e..758de96 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 5a7e93f..81aa1c0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Sat Aug 04 17:17:57 CST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/settings.gradle b/settings.gradle
index e7b4def..cc732ed 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,23 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "PhoneCallApp"
include ':app'