Próbuję stworzyć aplikację, która w przypadku połączenia przychodzącego na telefon chcę wykryć numer. Poniżej znajduje się to, co próbowałem, ale nie wykrywa połączeń przychodzących.
Chcę działać MainActivity
w tle, jak mogę to zrobić?
Udzieliłem pozwolenia w manifest
pliku.
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Czy jest jeszcze coś, co powinienem podać w manifeście?
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_layout);
}
public class myPhoneStateChangeListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
if (state == TelephonyManager.CALL_STATE_RINGING) {
String phoneNumber = incomingNumber;
}
}
}
}
Odpowiedzi:
Oto, czego używam, aby to zrobić:
Oczywisty:
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> <!--This part is inside the application--> <receiver android:name=".CallReceiver" > <intent-filter> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.NEW_OUTGOING_CALL" /> </intent-filter> </receiver>
Mój podstawowy detektor połączeń wielokrotnego użytku
package com.gabesechan.android.reusable.receivers; import java.util.Date; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; public abstract class PhonecallReceiver extends BroadcastReceiver { //The receiver will be recreated whenever android feels like it. We need a static variable to remember data between instantiations private static int lastState = TelephonyManager.CALL_STATE_IDLE; private static Date callStartTime; private static boolean isIncoming; private static String savedNumber; //because the passed incoming is only valid in ringing @Override public void onReceive(Context context, Intent intent) { //We listen to two intents. The new outgoing call only tells us of an outgoing call. We use it to get the number. if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) { savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER"); } else{ String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE); String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER); int state = 0; if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)){ state = TelephonyManager.CALL_STATE_IDLE; } else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){ state = TelephonyManager.CALL_STATE_OFFHOOK; } else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)){ state = TelephonyManager.CALL_STATE_RINGING; } onCallStateChanged(context, state, number); } } //Derived classes should override these to respond to specific events of interest protected abstract void onIncomingCallReceived(Context ctx, String number, Date start); protected abstract void onIncomingCallAnswered(Context ctx, String number, Date start); protected abstract void onIncomingCallEnded(Context ctx, String number, Date start, Date end); protected abstract void onOutgoingCallStarted(Context ctx, String number, Date start); protected abstract void onOutgoingCallEnded(Context ctx, String number, Date start, Date end); protected abstract void onMissedCall(Context ctx, String number, Date start); //Deals with actual events //Incoming call- goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up //Outgoing call- goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up public void onCallStateChanged(Context context, int state, String number) { if(lastState == state){ //No change, debounce extras return; } switch (state) { case TelephonyManager.CALL_STATE_RINGING: isIncoming = true; callStartTime = new Date(); savedNumber = number; onIncomingCallReceived(context, number, callStartTime); break; case TelephonyManager.CALL_STATE_OFFHOOK: //Transition of ringing->offhook are pickups of incoming calls. Nothing done on them if(lastState != TelephonyManager.CALL_STATE_RINGING){ isIncoming = false; callStartTime = new Date(); onOutgoingCallStarted(context, savedNumber, callStartTime); } else { isIncoming = true; callStartTime = new Date(); onIncomingCallAnswered(context, savedNumber, callStartTime); } break; case TelephonyManager.CALL_STATE_IDLE: //Went to idle- this is the end of a call. What type depends on previous state(s) if(lastState == TelephonyManager.CALL_STATE_RINGING){ //Ring but no pickup- a miss onMissedCall(context, savedNumber, callStartTime); } else if(isIncoming){ onIncomingCallEnded(context, savedNumber, callStartTime, new Date()); } else{ onOutgoingCallEnded(context, savedNumber, callStartTime, new Date()); } break; } lastState = state; } }
Następnie, aby go użyć, po prostu wyprowadź z niego klasę i zaimplementuj kilka prostych funkcji, niezależnie od typów wywołań, na których Ci zależy:
public class CallReceiver extends PhonecallReceiver { @Override protected void onIncomingCallReceived(Context ctx, String number, Date start) { // } @Override protected void onIncomingCallAnswered(Context ctx, String number, Date start) { // } @Override protected void onIncomingCallEnded(Context ctx, String number, Date start, Date end) { // } @Override protected void onOutgoingCallStarted(Context ctx, String number, Date start) { // } @Override protected void onOutgoingCallEnded(Context ctx, String number, Date start, Date end) { // } @Override protected void onMissedCall(Context ctx, String number, Date start) { // } }
Ponadto możesz zobaczyć napisane przeze mnie, dlaczego kod wygląda tak, jak jest na moim blogu . Link do streszczenia: https://gist.github.com/ftvs/e61ccb039f511eb288ee
EDYCJA: Zaktualizowano do prostszego kodu, ponieważ przerobiłem klasę na własny użytek
źródło
lastState
nie powinien być inicjalizowany naCALL_STATE_IDLE
. Brakuje mi kilku wywołań, gdy moja aplikacja zostaje zabita w obecnym stanieRINGING
. Ponieważ gdyIDLE
ponownie staje się po zakończeniu wywołania, zmienna statyczna jest ponownie inicjowanaCALL_STATE_IDLE
i nie robi nic. Więc tracimy odniesienie dolastState
.private MyPhoneStateListener phoneStateListener = new MyPhoneStateListener();
rejestrować
i wyrejestrować się
źródło
Z Androidem P - Api Level 28: Musisz uzyskać uprawnienie READ_CALL_LOG
Ograniczony dostęp do dzienników połączeń
Android P porusza
CALL_LOG
,READ_CALL_LOG
,WRITE_CALL_LOG
orazPROCESS_OUTGOING_CALLS
uprawnienia zPHONE
grupy uprawnień do nowejCALL_LOG
grupy uprawnień. Ta grupa zapewnia użytkownikom lepszą kontrolę i widoczność aplikacji, które potrzebują dostępu do poufnych informacji o połączeniach telefonicznych, takich jak odczytywanie zapisów rozmów telefonicznych i identyfikowanie numerów telefonów.Aby odczytać liczby z akcji intencji PHONE_STATE, potrzebujesz zarówno
READ_CALL_LOG
pozwolenia, jak iREAD_PHONE_STATE
pozwolenia . Aby czytać numery zonCallStateChanged()
, potrzebujesz terazREAD_CALL_LOG
tylko pozwolenia. Nie potrzebujesz jużREAD_PHONE_STATE
pozwolenia.źródło
READ_CALL_LOG
wAndroidManifest.xml
centrum uwagi na dodanie wniosek o pozwolenieMainActivity
.AKTUALIZACJA: Naprawdę niesamowity kod opublikowany przez Gabe Sechana nie działa, chyba że wyraźnie poprosisz użytkownika o przyznanie niezbędnych uprawnień. Oto kod, który możesz umieścić w swojej głównej aktywności, aby zażądać tych uprawnień:
if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { // Permission has not been granted, therefore prompt the user to grant permission ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE}, MY_PERMISSIONS_REQUEST_READ_PHONE_STATE); } if (getApplicationContext().checkSelfPermission(Manifest.permission.PROCESS_OUTGOING_CALLS) != PackageManager.PERMISSION_GRANTED) { // Permission has not been granted, therefore prompt the user to grant permission ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.PROCESS_OUTGOING_CALLS}, MY_PERMISSIONS_REQUEST_PROCESS_OUTGOING_CALLS); }
RÓWNIEŻ: Jak ktoś wspomniał w komentarzu pod postem Gabe'a , musisz dodać mały fragment kodu
android:enabled="true
do odbiornika, aby wykryć połączenia przychodzące, gdy aplikacja nie działa obecnie na pierwszym planie:<!--This part is inside the application--> <receiver android:name=".CallReceiver" android:enabled="true"> <intent-filter> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.NEW_OUTGOING_CALL" /> </intent-filter> </receiver>
źródło
może to pomóc, a także dodać wymagane uprawnienia
public class PhoneListener extends PhoneStateListener { private Context context; public static String getincomno; public PhoneListener(Context c) { Log.i("CallRecorder", "PhoneListener constructor"); context = c; } public void onCallStateChanged (int state, String incomingNumber) { if(!TextUtils.isEmpty(incomingNumber)){ // here for Outgoing number make null to get incoming number CallBroadcastReceiver.numberToCall = null; getincomno = incomingNumber; } switch (state) { case TelephonyManager.CALL_STATE_IDLE: break; case TelephonyManager.CALL_STATE_RINGING: Log.d("CallRecorder", "CALL_STATE_RINGING"); break; case TelephonyManager.CALL_STATE_OFFHOOK: break; } } }
źródło
Wystarczy zaktualizować odpowiedź Gabe'a Sechana. Jeśli manifest zawiera prośbę o uprawnienia do READ_CALL_LOG i READ_PHONE_STATE, onReceive zadzwoni DWUKROTNIE . Z których jeden zawiera EXTRA_INCOMING_NUMBER, a drugi nie. Musisz przetestować, który go ma i może to nastąpić w dowolnej kolejności.
https://developer.android.com/reference/android/telephony/TelephonyManager.html#ACTION_PHONE_STATE_CHANGED
źródło
Oto prosta metoda, dzięki której można uniknąć stosowania
PhonestateListener
i innych komplikacji.Więc jesteśmy otrzymaniu 3 wydarzenia z android, takich jak
RINGING
,OFFHOOK
iIDLE
. A także w celu uzyskania wszelkich możliwych stan połączenia, musimy określić nasze własne stany podobaRINGING
,OFFHOOK
,IDLE
,FIRST_CALL_RINGING
,SECOND_CALL_RINGING
. Potrafi obsłużyć wszystkie stany w rozmowie telefonicznej.Pomyśl w taki sposób, że otrzymujemy zdarzenia z Androida i zdefiniujemy nasze stany na wezwanie. Zobacz kod.
public class CallListening extends BroadcastReceiver { private static final String TAG ="broadcast_intent"; public static String incoming_number; private String current_state,previus_state,event; public static Boolean dialog= false; private Context context; private SharedPreferences sp,sp1; private SharedPreferences.Editor spEditor,spEditor1; public void onReceive(Context context, Intent intent) { //Log.d("intent_log", "Intent" + intent); dialog=true; this.context = context; event = intent.getStringExtra(TelephonyManager.EXTRA_STATE); incoming_number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); Log.d(TAG, "The received event : "+event+", incoming_number : " + incoming_number); previus_state = getCallState(context); current_state = "IDLE"; if(incoming_number!=null){ updateIncomingNumber(incoming_number,context); }else { incoming_number=getIncomingNumber(context); } switch (event) { case "RINGING": Log.d(TAG, "State : Ringing, incoming_number : " + incoming_number); if((previus_state.equals("IDLE")) || (previus_state.equals("FIRST_CALL_RINGING"))){ current_state ="FIRST_CALL_RINGING"; } if((previus_state.equals("OFFHOOK"))||(previus_state.equals("SECOND_CALL_RINGING"))){ current_state = "SECOND_CALL_RINGING"; } break; case "OFFHOOK": Log.d(TAG, "State : offhook, incoming_number : " + incoming_number); if((previus_state.equals("IDLE")) ||(previus_state.equals("FIRST_CALL_RINGING")) || previus_state.equals("OFFHOOK")){ current_state = "OFFHOOK"; } if(previus_state.equals("SECOND_CALL_RINGING")){ current_state ="OFFHOOK"; startDialog(context); } break; case "IDLE": Log.d(TAG, "State : idle and incoming_number : " + incoming_number); if((previus_state.equals("OFFHOOK")) || (previus_state.equals("SECOND_CALL_RINGING")) || (previus_state.equals("IDLE"))){ current_state="IDLE"; } if(previus_state.equals("FIRST_CALL_RINGING")){ current_state = "IDLE"; startDialog(context); } updateIncomingNumber("no_number",context); Log.d(TAG,"stored incoming number flushed"); break; } if(!current_state.equals(previus_state)){ Log.d(TAG, "Updating state from "+previus_state +" to "+current_state); updateCallState(current_state,context); } } public void startDialog(Context context) { Log.d(TAG,"Starting Dialog box"); Intent intent1 = new Intent(context, NotifyHangup.class); intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent1); } public void updateCallState(String state,Context context){ sp = PreferenceManager.getDefaultSharedPreferences(context); spEditor = sp.edit(); spEditor.putString("call_state", state); spEditor.commit(); Log.d(TAG, "state updated"); } public void updateIncomingNumber(String inc_num,Context context){ sp = PreferenceManager.getDefaultSharedPreferences(context); spEditor = sp.edit(); spEditor.putString("inc_num", inc_num); spEditor.commit(); Log.d(TAG, "incoming number updated"); } public String getCallState(Context context){ sp1 = PreferenceManager.getDefaultSharedPreferences(context); String st =sp1.getString("call_state", "IDLE"); Log.d(TAG,"get previous state as :"+st); return st; } public String getIncomingNumber(Context context){ sp1 = PreferenceManager.getDefaultSharedPreferences(context); String st =sp1.getString("inc_num", "no_num"); Log.d(TAG,"get incoming number as :"+st); return st; } }
źródło
@ Gabe Sechan, dziękujemy za kod. Działa dobrze, z wyjątkiem
onOutgoingCallEnded()
. Nigdy nie jest wykonywany. Testowane telefony to Samsung S5 i Trendy. Myślę, że są 2 błędy.1: brakuje pary nawiasów.
case TelephonyManager.CALL_STATE_IDLE: // Went to idle- this is the end of a call. What type depends on previous state(s) if (lastState == TelephonyManager.CALL_STATE_RINGING) { // Ring but no pickup- a miss onMissedCall(context, savedNumber, callStartTime); } else { // this one is missing if(isIncoming){ onIncomingCallEnded(context, savedNumber, callStartTime, new Date()); } else { onOutgoingCallEnded(context, savedNumber, callStartTime, new Date()); } } // this one is missing break;
2:
lastState
nie jest aktualizowany przez,state
jeśli znajduje się na końcu funkcji. W pierwszym wierszu tej funkcji należy go zastąpić znakiempublic void onCallStateChanged(Context context, int state, String number) { int lastStateTemp = lastState; lastState = state; // todo replace all the "lastState" by lastStateTemp from here. if (lastStateTemp == state) { //No change, debounce extras return; } //.... }
Dodatkowo umieściłem
lastState
isavedNumber
we wspólnych preferencjach, jak sugerowałeś.Właśnie przetestowałem to z powyższymi zmianami. Naprawiono błąd przynajmniej w moich telefonach.
źródło
Proszę użyć poniższego kodu. Pomoże Ci to uzyskać numer przychodzący z innymi szczegółami połączenia.
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <TextView android:id="@+id/call" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/hello_world" /> </RelativeLayout>
MainActivity.java
public class MainActivity extends Activity { private static final int MISSED_CALL_TYPE = 0; private TextView txtcall; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); txtcall = (TextView) findViewById(R.id.call); StringBuffer sb = new StringBuffer(); Cursor managedCursor = managedQuery(CallLog.Calls.CONTENT_URI, null, null, null, null); int number = managedCursor.getColumnIndex(CallLog.Calls.NUMBER); int type = managedCursor.getColumnIndex(CallLog.Calls.TYPE); int date = managedCursor.getColumnIndex(CallLog.Calls.DATE); int duration = managedCursor.getColumnIndex(CallLog.Calls.DURATION); sb.append("Call Details :"); while (managedCursor.moveToNext()) { String phNumber = managedCursor.getString(number); String callType = managedCursor.getString(type); String callDate = managedCursor.getString(date); Date callDayTime = new Date(Long.valueOf(callDate)); String callDuration = managedCursor.getString(duration); String dir = null; int dircode = Integer.parseInt(callType); switch (dircode) { case CallLog.Calls.OUTGOING_TYPE: dir = "OUTGOING"; break; case CallLog.Calls.INCOMING_TYPE: dir = "INCOMING"; break; case CallLog.Calls.MISSED_TYPE: dir = "MISSED"; break; } sb.append("\nPhone Number:--- " + phNumber + " \nCall Type:--- " + dir + " \nCall Date:--- " + callDayTime + " \nCall duration in sec :--- " + callDuration); sb.append("\n----------------------------------"); } managedCursor.close(); txtcall.setText(sb); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
oraz w swoim manifeście prośby o następujące uprawnienia:
<uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.READ_LOGS"/>
źródło
Potrzebujesz
ACTION_PHONE_STATE_CHANGED
odbiornika BroadcastReceiver dla To zadzwoni do twojego odebranego połączenia, gdy stan telefonu zmieni się z bezczynności, dzwonienia, podniesienia słuchawki, więc z poprzedniej wartości i nowej wartości można wykryć, czy jest to połączenie przychodzące / wychodzące.Wymagane pozwolenie to:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Ale jeśli chcesz również otrzymać EXTRA_INCOMING_NUMBER w tej transmisji, potrzebujesz innego uprawnienia: „android.permission.READ_CALL_LOG”
A kod coś takiego:
val receiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Log.d(TAG, "onReceive") } } override fun onResume() { val filter = IntentFilter() filter.addAction("android.intent.action.PHONE_STATE") registerReceiver(receiver, filter) super.onResume() } override fun onPause() { unregisterReceiver(receiver) super.onPause() }
aw klasie odbiornika możemy uzyskać aktualny stan, czytając intencję w następujący sposób:
intent.extras["state"]
wynikiem dodatków może być:
W przypadku transmisji PHONE_STATE nie musimy używać uprawnienia PROCESS_OUTGOING_CALLS ani wycofanej akcji NEW_OUTGOING_CALL.
źródło