סקירה כללית של מסגרת הטלקום

מסגרת Android Telecom (שנקראת גם 'Telecom') מנהלת שיחות אודיו ושיחות וידאו במכשיר עם Android. הנתונים האלה כוללים שיחות שמבוססות על כרטיס SIM, כמו שיחות שמשתמשות במסגרת הטלפוניה, ושיחות VoIP שמטמיעות את ספריית Jetpack‏ Core-Telecom.

הרכיבים העיקריים שמנוהלים על ידי Telecom הם ConnectionService ו-InCallService.

הטמעה של ConnectionService מבוססת על טכנולוגיות כמו PSTN כדי לחבר שיחות לצדדים אחרים. ההטמעה הנפוצה ביותר של ConnectionService בטלפון היא ConnectionService של טלפוניה. הוא מחבר שיחות דרך הספק.

הטמעה של InCallService מספקת ממשק משתמש לשיחות שמנוהלות על ידי חברת התקשורת, ומאפשרת למשתמש לשלוט בשיחות האלה וליצור איתן אינטראקציה. ההטמעה הנפוצה ביותר של InCallService היא אפליקציית הטלפון שכלולה בחבילה עם המכשיר.

חברת התקשורת פועלת כמרכז טלפוניה. הוא מפנה את השיחות שהטמעות ConnectionService מספקות לממשקי המשתמש של מבצעי הקריאה שהטמעות InCallService מספקות.

כדאי להטמיע את ממשקי ה-API של Telecom מהסיבות הבאות:

יצירת אפליקציית טלפון חלופית

כדי ליצור תחליף לאפליקציית הטלפון שמוגדרת כברירת מחדל במכשיר Android, צריך להטמיע את ה-API של InCallService. ההטמעה צריכה לעמוד בדרישות הבאות:

  • לא יכולה להיות לה יכולת ביצוע שיחות, והיא צריכה להכיל רק את ממשק המשתמש לשיחות.
  • הוא צריך לטפל בכל השיחות שמערכת Telecom מודעת אליהן, ולא להניח הנחות לגבי אופי השיחות. לדוגמה, אסור להניח שהשיחות הן שיחות טלפוניה שמבוססות על כרטיס SIM, או להחיל הגבלות על שיחות שמבוססות על ConnectionService אחד, כמו אכיפת הגבלות טלפוניה על שיחות וידאו.

מידע נוסף זמין במאמר InCallService.

שילוב של פתרון לשיחות

כדי לשלב את פתרון השיחות ב-Android, יש לכם את האפשרויות הבאות:

  • הטמעת ספריית Core-Telecom Jetpack בניהול עצמי: האפשרות הזו אידיאלית למפתחים של אפליקציות שיחה עצמאיות שלא רוצים להציג את השיחות שלהם באפליקציית הטלפון שמוגדרת כברירת מחדל, או שרוצים שהשיחות האחרות לא יוצגו בממשק המשתמש שלהם.

    כשמשלבים עם ספריית Jetpack‏ Core-Telecom, אפשר לעזור לאפליקציה לפעול לא רק עם שיחות טלפון במערכת במכשיר, אלא גם עם אפליקציות שיחות עצמאיות אחרות שמשתלבות עם Telecom. הספרייה Core-Telecom מנהלת גם את הניתוב והמיקוד של האודיו. פרטים נוספים זמינים במאמר פיתוח אפליקציית שיחות.

  • הטמעת ConnectionService API המנוהל: האפשרות הזו מאפשרת לפתח פתרון לשיחות שמסתמך על אפליקציית הטלפון הקיימת במכשיר כדי לספק את ממשק המשתמש לשיחות. דוגמאות: הטמעה של צד שלישי של שירותי שיחות SIP ו-VoIP. למידע נוסף, ראו getDefaultDialerPackage().

    כרטיס ConnectionService מספק רק את האמצעים לחיבור שיחות. אין לו ממשק משתמש משויך.

  • הטמעת InCallService ו-ConnectionService API: אפשרות זו אידיאלית אם אתם רוצים ליצור פתרון לשיחות מבוסס-ConnectionService משלכם, עם ממשק משתמש משלו, וגם להציג את כל השיחות האחרות ב-Android באותו ממשק משתמש. כשמשתמשים בגישה הזו, ההטמעה של InCallService לא יכולה להסתמך על הנחות לגבי מקורות הקריאות שהיא מציגה. בנוסף, ההטמעה של ConnectionService צריכה להמשיך לפעול גם אם אפליקציית הטלפון שמוגדרת כברירת מחדל לא תהיה InCallService בהתאמה אישית.

סינון שיחות

במכשירים עם Android 10 ואילך (רמת API 29 ואילך), האפליקציה יכולה לזהות שיחות ממספרים שלא נמצאים בפנקס הכתובות של המשתמש כשיחות ספאם פוטנציאליות. המשתמשים יכולים לבחור ששיחות ספאם נדחות בשקט. כדי לספק למשתמשים שקיפות רבה יותר כשהם מחמיצים שיחות, המידע על השיחות החסומים מתועד ביומן השיחות. השימוש ב-API של Android 10 מבטל את הצורך לקבל מהמשתמש את ההרשאה READ_CALL_LOG כדי לספק את הפונקציונליות של סינון שיחות ושל זיהוי מבצע השיחה.

אתם משתמשים בהטמעה של CallScreeningService כדי לסנן שיחות. צריך להפעיל את הפונקציה onScreenCall() בכל שיחה נכנסת או יוצאת חדשה, אם המספר לא נמצא ברשימת אנשי הקשר של המשתמש. אפשר לבדוק את האובייקט Call.Details כדי לקבל מידע על הקריאה. באופן ספציפי, הפונקציה getCallerNumberVerificationStatus() כוללת מידע מספק הרשת לגבי המספר השני. אם סטטוס האימות נכשל, סביר להניח שהשיחה הגיעה ממספר לא חוקי או שמדובר בשיחת ספאם.

Kotlin

class ScreeningService : CallScreeningService() {
    // This function is called when an ingoing or outgoing call
    // is from a number not in the user's contacts list
    override fun onScreenCall(callDetails: Call.Details) {
        // Can check the direction of the call
        val isIncoming = callDetails.callDirection == Call.Details.DIRECTION_INCOMING

        if (isIncoming) {
            // the handle (e.g. phone number) that the Call is currently connected to
            val handle: Uri = callDetails.handle

            // determine if you want to allow or reject the call
            when (callDetails.callerNumberVerificationStatus) {
                Connection.VERIFICATION_STATUS_FAILED -> {
                    // Network verification failed, likely an invalid/spam call.
                }
                Connection.VERIFICATION_STATUS_PASSED -> {
                    // Network verification passed, likely a valid call.
                }
                else -> {
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED.
                }
            }
        }
    }
}

Java

class ScreeningService extends CallScreeningService {
    @Override
    public void onScreenCall(@NonNull Call.Details callDetails) {
        boolean isIncoming = callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING;

        if (isIncoming) {
            Uri handle = callDetails.getHandle();

            switch (callDetails.getCallerNumberVerificationStatus()) {
                case Connection.VERIFICATION_STATUS_FAILED:
                    // Network verification failed, likely an invalid/spam call.
                    break;
                case Connection.VERIFICATION_STATUS_PASSED:
                    // Network verification passed, likely a valid call.
                    break;
                default:
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED
            }
        }
    }
}

מגדירים את הפונקציה onScreenCall() כך שתפעיל את respondToCall() כדי להורות למערכת איך להגיב לשיחה החדשה. הפונקציה מקבלת פרמטר CallResponse שאפשר להשתמש בו כדי להורות למערכת לחסום את השיחה, לדחות אותה כאילו המשתמש דחה אותה או להשתיק אותה. אפשר גם להורות למערכת לדלג על ההוספה של השיחה הזו ליומן השיחות של המכשיר.

Kotlin

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
val response = CallResponse.Builder()
    // Sets whether the incoming call should be blocked.
    .setDisallowCall(false)
    // Sets whether the incoming call should be rejected as if the user did so manually.
    .setRejectCall(false)
    // Sets whether ringing should be silenced for the incoming call.
    .setSilenceCall(false)
    // Sets whether the incoming call should not be displayed in the call log.
    .setSkipCallLog(false)
    // Sets whether a missed call notification should not be shown for the incoming call.
    .setSkipNotification(false)
    .build()

// Call this function to provide your screening response.
respondToCall(callDetails, response)

Java

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
CallResponse.Builder response = new CallResponse.Builder();
// Sets whether the incoming call should be blocked.
response.setDisallowCall(false);
// Sets whether the incoming call should be rejected as if the user did so manually.
response.setRejectCall(false);
// Sets whether ringing should be silenced for the incoming call.
response.setSilenceCall(false);
// Sets whether the incoming call should not be displayed in the call log.
response.setSkipCallLog(false);
// Sets whether a missed call notification should not be shown for the incoming call.
response.setSkipNotification(false);

// Call this function to provide your screening response.
respondToCall(callDetails, response.build());

כדי שהמערכת תוכל להפעיל את ההטמעה של CallScreeningService בצורה נכונה, צריך לרשום אותה בקובץ המניפסט עם מסנן ה-Intent וההרשאה המתאימים.

<service
    android:name=".ScreeningService"
    android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallScreeningService" />
    </intent-filter>
</service>

הפניה מחדש של שיחה

במכשירים עם Android מגרסה 10 ואילך, ניהול כוונת השיחה שונה מאשר במכשירים עם Android מגרסה 9 ומטה. ב-Android מגרסה 10 ואילך, השידור ACTION_NEW_OUTGOING_CALL הוצא משימוש והוחלף ב-API‏ CallRedirectionService. CallRedirectionService מספק ממשקים שאפשר להשתמש בהם כדי לשנות שיחות יוצאות שמבוצעות על ידי פלטפורמת Android. לדוגמה, אפליקציות צד שלישי עשויות לבטל שיחות ולנתב אותן מחדש דרך VoIP.

Kotlin

class RedirectionService : CallRedirectionService() {
    override fun onPlaceCall(
        handle: Uri,
        initialPhoneAccount: PhoneAccountHandle,
        allowInteractiveResponse: Boolean
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        val callShouldProceed = true
        val callShouldRedirect = false
        when {
            callShouldProceed -> {
                placeCallUnmodified()
            }
            callShouldRedirect -> {
                // Update the URI to point to a different phone number or modify the
                // PhoneAccountHandle and redirect.
                redirectCall(handle, initialPhoneAccount, true)
            }
            else -> {
                cancelCall()
            }
        }
    }
}

Java

class RedirectionService extends CallRedirectionService {
    @Override
    public void onPlaceCall(
            @NonNull Uri handle,
            @NonNull PhoneAccountHandle initialPhoneAccount,
            boolean allowInteractiveResponse
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        // Your app should implement this logic to determine the redirection.
        boolean callShouldProceed = true;
        boolean callShouldRedirect = false;
        if (callShouldProceed) {
            placeCallUnmodified();
        } else if (callShouldRedirect) {
            // Update the URI to point to a different phone number or modify the
            // PhoneAccountHandle and redirect.
            redirectCall(handle, initialPhoneAccount, true);
        } else {
            cancelCall();
        }
    }
}

צריך לרשום את השירות הזה במניפסט כדי שהמערכת תוכל להפעיל אותו בצורה תקינה.

<service
    android:name=".RedirectionService"
    android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallRedirectionService"/>
    </intent-filter>
</service>

כדי להשתמש בשירות ניתוב, האפליקציה צריכה לבקש את התפקיד 'ניתוב שיחות' מ-RoleManager. המשתמש יתבקש לאשר לאפליקציה לטפל בהפניות של שיחות. אם לא הוקצה לאפליקציה התפקיד הזה, לא תתבצע שימוש בשירות ההפניה האוטומטית.

כדאי לבדוק אם לאפליקציה יש את התפקיד הזה כשהמשתמש מפעיל אותה, כדי שתוכלו לבקש אותו לפי הצורך. אתם מריצים כוונה שנוצרה על ידי RoleManager, לכן חשוב לשנות את ברירת המחדל של הפונקציה onActivityResult() כדי לטפל בבחירה של המשתמש.

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
            // Check if the app needs to register call redirection role.
            val shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION)
            if (shouldRequestRole) {
                val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION)
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE)
            }
        }
    }

    companion object {
        private const val REDIRECT_ROLE_REQUEST_CODE = 1
    }
}

Java

class MainActivity extends AppCompatActivity {
    private static final int REDIRECT_ROLE_REQUEST_CODE = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);
            // Check if the app needs to register call redirection role.
            boolean shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION);
            if (shouldRequestRole) {
                Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION);
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE);
            }
        }
    }
}