Android - Broadcast receiver not detecting PHONE_STATE_CHANGED when app is closed

Viewed 31

I am trying to develop an app that detects incoming call and announce the name of the caller. The broadcast receiver that I have used works fine when the app is in foreground. But when I close the app, it doesn't detect the phone state although I have declared it in my manifest.

Manifest.xml:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-feature android:name="android.hardware.telephony"/>
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<uses-permission android:name="android.permission.READ_SMS" />

<uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
    tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="oppo.permission.OPPO_COMPONENT_SAFE"/>
<uses-permission android:name="com.huawei.permission.external_app_settings.USE_COMPONENT"/>

<queries>
    <intent>
        <action android:name="android.intent.action.TTS_SERVICE" />
    </intent>
</queries>

<application
    android:allowBackup="false"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:screenOrientation="portrait"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.CallAnnouncer"
    tools:targetApi="31">
    <activity
        android:name=".ExcludeActivity"
        android:exported="false" />
    <activity
        android:name=".SMSActivity"
        android:exported="false" />
    <activity
        android:name=".CallActivity"
        android:exported="false" />
    <activity
        android:name=".PermissionActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <receiver
        android:name=".CallReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.PHONE_STATE" />
        </intent-filter>
    </receiver>

    <receiver
        android:name=".StartMyServiceAtBootReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>

    <service android:name=".Speaker"
        android:enabled="true"/>

    <activity
        android:name=".MainActivity"
        android:exported="true" />
</application>

CallReceiver.java

Context context1;
String savedNumber, name, incomeNumber;
private static int lastState = TelephonyManager.CALL_STATE_IDLE;
static boolean isIncoming;
Boolean isAdded = false;
SharedPreferences prefs;

@SuppressLint("UnsafeProtectedBroadcastReceiver")
@Override
public void onReceive(Context context, Intent intent) {
    context1 = context;

    TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    telephony.listen(new PhoneStateListener() {
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
                prefs = context.getSharedPreferences("MyPref", 0);
                SharedPreferences.Editor editor = prefs.edit();
                isAdded = prefs.getBoolean("added", false);
                incomeNumber = prefs.getString("number", null);
                if (isAdded) {
                    onStateChanged(context, state, incomeNumber, intent);
                    editor.putString("number", incomeNumber);
                } else {
                    onStateChanged(context, state, incomingNumber, intent);
                    editor.putString("number", incomingNumber);
                }
                editor.putBoolean("added", !isAdded);
                editor.apply();
            } else
                incomeNumber = incomingNumber;
            onStateChanged(context, state, incomingNumber, intent);
        }
    }, PhoneStateListener.LISTEN_CALL_STATE);
}

public void onStateChanged(Context context, int state, String number, Intent intent) {
    if (lastState == state) {
        //No change, debounce extras
        return;
    }
    switch (state) {
        case TelephonyManager.CALL_STATE_RINGING:
            isIncoming = true;
            Date callStartTime = new Date();
            savedNumber = number;
            onIncomingCallStarted(context, savedNumber, callStartTime, intent);
            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, intent);
            }

            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
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
                    onMissedCall(context, incomeNumber, new Date(), intent);
                } else
                    onMissedCall(context, savedNumber, new Date(), intent);
            } else if (isIncoming) {
                onIncomingCallEnded(context, savedNumber, new Date(), intent);
            } else {
                onOutgoingCallEnded(context, savedNumber, new Date(), intent);
            }

            break;
    }

    lastState = state;
}

protected void onIncomingCallStarted(Context ctx, String number, Date start, Intent intent) {
    name = getContactName(number);
    Intent ttsService = new Intent(ctx, Speaker.class);
    ttsService.putExtra("text", "You have call from " + name);
    ctx.startService(ttsService);
}

protected void onMissedCall(Context ctx, String number, Date start, Intent intent) {
    Intent ttsService = new Intent(ctx, Speaker.class);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        ttsService.putExtra("text", "You have a missed call");
    } else {
        name = getContactName(number);
        ttsService.putExtra("text", "You have missed call from " + name);
    }
    ctx.startService(ttsService);
    //            saveData(ctx, number, intent, "Missed Call");
}

protected void onOutgoingCallStarted(Context ctx, String number, Date start, Intent intent) {
    name = getContactName(number);
    Log.d("TAG", "Outgoing Call to " + name);
}

protected void onIncomingCallEnded(Context ctx, String number, Date start, Intent intent) {
    name = getContactName(number);
    Log.d("TAG", "Incoming call ended of " + name);
}

protected void onOutgoingCallEnded(Context ctx, String number, Date start, Intent intent) {
    name = getContactName(number);
    Log.d("TAG", "Outgoing Call ended of " + name);
}

private String getContactName(String phone) {
    ContentResolver cr = context1.getContentResolver();
    Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phone));
    Cursor cursor = cr.query(uri, new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);
    if (cursor.moveToFirst()) {
        name = cursor.getString(0);
    } else {
        name = "unknown number";
    }

    if (!cursor.isClosed()) {
        cursor.close();
    }

    return name;
}

SpeakerService.java:

TextToSpeech tts = null;
String spokenText;
Boolean isInit = false;
HashMap<String, String> hash = new HashMap<>();

private static final String TAG = "TTS_Service";

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent.getExtras() != null)
        spokenText = intent.getStringExtra("text");
    tts = new TextToSpeech(this, this);
    return START_STICKY;
}

@Override
public void onInit(int i) {
    if (i == TextToSpeech.SUCCESS) {
        Log.d("TAG", "onInit: TextToSpeech Success");
        int result = tts.setLanguage(Locale.US);
        if (result != TextToSpeech.LANG_MISSING_DATA && result != TextToSpeech.LANG_NOT_SUPPORTED) {
            Log.d("TAG", "onInit: speaking........");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                hash.put(TextToSpeech.Engine.KEY_PARAM_STREAM,
                        String.valueOf(AudioManager.STREAM_NOTIFICATION));
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                    tts.speak(spokenText, TextToSpeech.QUEUE_FLUSH, null, null);
                } else {
                    tts.speak(spokenText, TextToSpeech.QUEUE_FLUSH, null);
                }
            }
            isInit = true;
        }
    } else {
        Log.d("TAG", "onInit: TTS initialization failed");
    }
}

@Override
public void onDestroy() {
    if (tts != null) {
        tts.stop();
        tts.shutdown();
    }
    super.onDestroy();
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

}

0 Answers
Related