จะรับสายเรียกเข้าแบบเป็นโปรแกรมใน Android 5.0 (Lollipop) ได้อย่างไร


89

ขณะที่ฉันพยายามสร้างหน้าจอที่กำหนดเองสำหรับสายเรียกเข้าฉันพยายามรับสายเรียกเข้าโดยใช้โปรแกรม ฉันใช้รหัสต่อไปนี้ แต่ใช้งานไม่ได้ใน Android 5.0

// Simulate a press of the headset button to pick up the call
Intent buttonDown = new Intent(Intent.ACTION_MEDIA_BUTTON);             
buttonDown.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
context.sendOrderedBroadcast(buttonDown, "android.permission.CALL_PRIVILEGED");

// froyo and beyond trigger on buttonUp instead of buttonDown
Intent buttonUp = new Intent(Intent.ACTION_MEDIA_BUTTON);               
buttonUp.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
context.sendOrderedBroadcast(buttonUp, "android.permission.CALL_PRIVILEGED");

โอ้ผู้ชายทำไมวิ่งเข้าไปในนี้เพียงแค่เลื่อนผู้ชาย! ดูง่ายกว่าสำหรับฉัน \ m /
nobalG

ฉันกำลังสร้างหน้าจอสายเรียกเข้าที่กำหนดเองสำหรับผู้ใช้ Android
maveroid

2
ทุกคน? ฉันสนใจเรื่องนี้ด้วย! พยายามหลายอย่าง แต่ไม่ได้ผล: /
Arthur

1
@nobalG เขากำลังพูดแบบเป็นโปรแกรม
dsharew

1
@maveroid คุณคิดวิธีแก้ปัญหาสำหรับ Android 5.0 หรือไม่
arthursfreire

คำตอบ:


158

อัปเดตด้วย Android 8.0 Oreo

แม้ว่าในตอนแรกจะมีการถามคำถามเกี่ยวกับการสนับสนุน Android L แต่ดูเหมือนว่าผู้คนจะตอบคำถามและคำตอบนี้ดังนั้นจึงควรอธิบายถึงการปรับปรุงที่แนะนำใน Android 8.0 Oreo วิธีการที่เข้ากันได้แบบย้อนหลังยังคงอธิบายไว้ด้านล่าง

อะไรเปลี่ยนไป?

เริ่มต้นด้วยAndroid 8.0 Oreoที่PHONEกลุ่มได้รับอนุญาตนอกจากนี้ยังมีANSWER_PHONE_CALLSได้รับอนุญาต ตามชื่อการอนุญาตที่แนะนำการถือจะช่วยให้แอปของคุณรับสายเรียกเข้าโดยทางโปรแกรมผ่านการเรียก API ที่เหมาะสมโดยไม่ต้องแฮ็กระบบโดยใช้การสะท้อนหรือจำลองผู้ใช้

เราจะใช้ประโยชน์จากการเปลี่ยนแปลงนี้อย่างไร?

คุณควรตรวจสอบเวอร์ชันของระบบที่รันไทม์หากคุณรองรับ Android เวอร์ชันเก่าเพื่อที่คุณจะสามารถห่อหุ้มการเรียก API ใหม่นี้ได้ในขณะที่ยังคงรองรับ Android เวอร์ชันเก่าเหล่านั้น คุณควรติดตามการขอสิทธิ์ในขณะทำงานเพื่อรับสิทธิ์ใหม่ในระหว่างรันไทม์ตามมาตรฐานของ Android เวอร์ชันใหม่กว่า

หลังจากที่ได้รับอนุญาตให้แอปของคุณเพียงแค่มีการเพียงแค่เรียกTelecomManager ของacceptRingingCallวิธี การเรียกใช้พื้นฐานมีลักษณะดังนี้:

TelecomManager tm = (TelecomManager) mContext
        .getSystemService(Context.TELECOM_SERVICE);

if (tm == null) {
    // whether you want to handle this is up to you really
    throw new NullPointerException("tm == null");
}

tm.acceptRingingCall();

วิธีที่ 1: TelephonyManager.answerRingingCall ()

เมื่อคุณควบคุมอุปกรณ์ได้ไม่ จำกัด

นี่คืออะไร?

มี TelephonyManager.answerRingingCall () ซึ่งเป็นวิธีการภายในที่ซ่อนอยู่ ทำงานเป็นสะพานสำหรับ ITelephony.answerRingingCall () ซึ่งได้รับการพูดคุยเกี่ยวกับ interwebs และดูเหมือนว่าจะมีแนวโน้มในตอนเริ่มต้น มันเป็นไม่ได้ที่มีอยู่บน4.4.2_r1ขณะที่มันถูกนำมาใช้เฉพาะในการกระทำ83da75dสำหรับ Android 4.4 KitKat ( สาย 1537 บน 4.4.3_r1 ) และต่อมา "รู้" ในการกระทำf1e1e77สำหรับอมยิ้ม ( สาย 3138 บน 5.0.0_r1 ) เนื่องจากวิธีการที่ โครงสร้าง Git ซึ่งหมายความว่าหากคุณไม่สนับสนุนเฉพาะอุปกรณ์ที่มี Lollipop ซึ่งอาจเป็นการตัดสินใจที่ไม่ดีเนื่องจากมีส่วนแบ่งการตลาดเพียงเล็กน้อย ณ ตอนนี้คุณยังคงต้องระบุวิธีการสำรองหากไปตามเส้นทางนี้

เราจะใช้สิ่งนี้อย่างไร?

เนื่องจากวิธีการที่เป็นปัญหาถูกซ่อนจากการใช้แอปพลิเคชัน SDK คุณจึงต้องใช้การสะท้อนเพื่อตรวจสอบและใช้วิธีการแบบไดนามิกระหว่างรันไทม์ หากคุณไม่คุ้นเคยกับการสะท้อนคุณสามารถอ่านได้อย่างรวดเร็วว่าการสะท้อนคืออะไรและเหตุใดจึงมีประโยชน์ . คุณยังสามารถเจาะลึกข้อมูลเฉพาะได้ที่Trail: The Reflection APIหากคุณสนใจที่จะทำเช่นนั้น

และรหัสนั้นมีลักษณะอย่างไร?

// set the logging tag constant; you probably want to change this
final String LOG_TAG = "TelephonyAnswer";

TelephonyManager tm = (TelephonyManager) mContext
        .getSystemService(Context.TELEPHONY_SERVICE);

try {
    if (tm == null) {
        // this will be easier for debugging later on
        throw new NullPointerException("tm == null");
    }

    // do reflection magic
    tm.getClass().getMethod("answerRingingCall").invoke(tm);
} catch (Exception e) {
    // we catch it all as the following things could happen:
    // NoSuchMethodException, if the answerRingingCall() is missing
    // SecurityException, if the security manager is not happy
    // IllegalAccessException, if the method is not accessible
    // IllegalArgumentException, if the method expected other arguments
    // InvocationTargetException, if the method threw itself
    // NullPointerException, if something was a null value along the way
    // ExceptionInInitializerError, if initialization failed
    // something more crazy, if anything else breaks

    // TODO decide how to handle this state
    // you probably want to set some failure state/go to fallback
    Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e);
}

ดีเกินจริง!

จริงๆแล้วมีปัญหาเล็กน้อยอย่างหนึ่ง วิธีนี้ควรจะทำงานอย่างเต็มที่ แต่ผู้จัดการการรักษาความปลอดภัยที่ต้องการโทรติดต่อการระงับandroid.permission.MODIFY_PHONE_STATE การอนุญาตนี้อยู่ในขอบเขตของคุณสมบัติที่เป็นเอกสารบางส่วนของระบบเนื่องจากไม่คาดว่าบุคคลที่สามจะสัมผัส (ดังที่คุณเห็นจากเอกสารประกอบ) คุณสามารถลองเพิ่ม a <uses-permission>ได้ แต่จะไม่เป็นผลดีเพราะระดับการป้องกันสำหรับสิทธิ์นี้คือลายเซ็น | ระบบ ( ดูบรรทัด 1201 ของ core / AndroidManifest ที่ 5.0.0_r1 )

คุณสามารถอ่านเอกสารฉบับที่ 34785: อัปเดต android: protectionLevelซึ่งสร้างขึ้นในปี 2012 เพื่อดูว่าเราขาดรายละเอียดเกี่ยวกับ "ไวยากรณ์ไปป์" ที่เฉพาะเจาะจง แต่จากการทดลองรอบ ๆ ดูเหมือนว่าจะต้องทำงานเป็น "AND" ซึ่งหมายถึง แฟล็กที่ระบุต้องได้รับการตอบสนองเพื่อการอนุญาตที่จะได้รับ การทำงานภายใต้สมมติฐานนั้นหมายความว่าคุณต้องมีใบสมัครของคุณ:

  1. ติดตั้งเป็นแอปพลิเคชันระบบ

    สิ่งนี้ควรจะใช้ได้ดีและสามารถทำได้โดยขอให้ผู้ใช้ติดตั้งโดยใช้ ZIP ในการกู้คืนเช่นเมื่อรูทหรือติดตั้งแอพ Google บน ROM แบบกำหนดเองที่ยังไม่มีแพ็กเกจ

  2. ลงนามด้วยลายเซ็นเดียวกันกับเฟรมเวิร์ก / ฐานหรือที่เรียกว่าระบบหรือที่เรียกว่า ROM

    นี่คือจุดที่เกิดปัญหาขึ้น ในการทำเช่นนี้คุณต้องมีกุญแจที่ใช้ในการเซ็นชื่อกรอบ / ฐาน คุณไม่เพียง แต่ต้องเข้าถึงคีย์ของ Google สำหรับอิมเมจโรงงานของ Nexus เท่านั้น แต่คุณยังต้องเข้าถึงคีย์ของ OEM และ ROM ของนักพัฒนาซอฟต์แวร์อื่น ๆ ด้วย สิ่งนี้ดูไม่น่าจะเป็นไปได้ดังนั้นคุณสามารถให้แอปพลิเคชันของคุณลงนามด้วยคีย์ระบบได้โดยการสร้าง ROM ที่กำหนดเองและขอให้ผู้ใช้ของคุณเปลี่ยนไปใช้ (ซึ่งอาจทำได้ยาก) หรือโดยการหาช่องโหว่ที่สามารถข้ามระดับการป้องกันสิทธิ์ได้ (ซึ่งอาจจะยากเช่นกัน)

นอกจากนี้พฤติกรรมนี้ดูเหมือนจะเกี่ยวข้องกับปัญหา 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS ไม่ทำงานอีกต่อไปซึ่งใช้ระดับการป้องกันเดียวกันพร้อมกับค่าสถานะการพัฒนาที่ไม่มีเอกสารเช่นกัน

การทำงานกับ TelephonyManager ฟังดูดี แต่จะไม่ได้ผลเว้นแต่คุณจะได้รับอนุญาตที่เหมาะสมซึ่งไม่ใช่เรื่องง่ายที่จะทำในทางปฏิบัติ

แล้วการใช้ TelephonyManager ด้วยวิธีอื่น ๆ ล่ะ?

น่าเศร้าที่ดูเหมือนว่าคุณจะต้องถือandroid.permission.MODIFY_PHONE_STATEเพื่อใช้เครื่องมือเจ๋ง ๆ ซึ่งหมายความว่าคุณจะมีปัญหาในการเข้าถึงวิธีการเหล่านั้น


วิธีที่ 2: บริการโทรรหัสบริการ

สำหรับเวลาที่คุณสามารถทดสอบได้ว่าบิวด์ที่ทำงานบนอุปกรณ์จะทำงานกับรหัสที่ระบุ

หากไม่สามารถโต้ตอบกับ TelephonyManager ได้ก็ยังมีความเป็นไปได้ที่จะโต้ตอบกับบริการผ่านทางserviceปฏิบัติการ

วิธีนี้ทำงานอย่างไร?

ค่อนข้างเรียบง่าย แต่มีเอกสารเกี่ยวกับเส้นทางนี้น้อยกว่าเส้นทางอื่น ๆ เราทราบดีว่าไฟล์ปฏิบัติการนั้นมีอาร์กิวเมนต์สองตัวคือชื่อบริการและรหัส

  • ชื่อบริการของเราต้องการที่จะใช้เป็นโทรศัพท์

    service listดังจะเห็นได้จากการทำงาน

  • รหัสเราต้องการที่จะใช้งานดูเหมือนจะได้รับ6แต่ดูเหมือนว่าจะเป็นตอนที่ 5

    ดูเหมือนว่าจะใช้IBinder.FIRST_CALL_TRANSACTION + 5 สำหรับหลายเวอร์ชันในขณะนี้ (จาก1.5_r4ถึง4.4.4_r1 ) แต่ในระหว่างการทดสอบภายในเครื่องรหัส 5 ทำงานเพื่อรับสายเรียกเข้า เนื่องจาก Lollipo เป็นการอัปเดตครั้งใหญ่รอบด้านจึงมีการเปลี่ยนแปลงภายในที่เข้าใจได้เช่นกัน

ผลลัพธ์นี้มีคำสั่งservice call phone 5.

เราจะใช้ประโยชน์จากโปรแกรมนี้ได้อย่างไร

Java

โค้ดต่อไปนี้เป็นการใช้งานคร่าวๆที่สร้างขึ้นเพื่อใช้เป็นหลักฐานยืนยันแนวคิด หากคุณต้องการที่จะไปข้างหน้าและใช้วิธีการนี้จริงคุณอาจต้องการตรวจสอบแนวทางสำหรับการใช้งานปราศจากปัญหา suและอาจเปลี่ยนไปได้รับการพัฒนามากขึ้นอย่างเต็มที่libsuperuserโดยChainfire

try {
    Process proc = Runtime.getRuntime().exec("su");
    DataOutputStream os = new DataOutputStream(proc.getOutputStream());

    os.writeBytes("service call phone 5\n");
    os.flush();

    os.writeBytes("exit\n");
    os.flush();

    if (proc.waitFor() == 255) {
        // TODO handle being declined root access
        // 255 is the standard code for being declined root for SU
    }
} catch (IOException e) {
    // TODO handle I/O going wrong
    // this probably means that the device isn't rooted
} catch (InterruptedException e) {
    // don't swallow interruptions
    Thread.currentThread().interrupt();
}

ประจักษ์

<!-- Inform the user we want them root accesses. -->
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>

สิ่งนี้ต้องการการเข้าถึงรูทจริงๆหรือไม่?

น่าเศร้าที่ดูเหมือนเป็นเช่นนั้น คุณสามารถลองใช้Runtime.execได้ แต่ฉันไม่สามารถโชคดีกับเส้นทางนั้นได้

มั่นคงแค่ไหน?

ฉันดีใจที่คุณถาม เนื่องจากไม่ได้รับการจัดทำเป็นเอกสารจึงสามารถแบ่งเวอร์ชันต่างๆได้ดังที่แสดงในความแตกต่างของรหัสที่ปรากฏด้านบน ชื่อบริการควรจะอยู่ในโทรศัพท์ในรุ่นต่างๆ แต่สำหรับสิ่งที่เรารู้ค่ารหัสสามารถเปลี่ยนแปลงได้ในหลาย ๆ รุ่นของเวอร์ชันเดียวกัน (การปรับเปลี่ยนภายในโดยกล่าวคือสกินของ OEM) ซึ่งจะทำลายวิธีการที่ใช้ ดังนั้นจึงควรค่าแก่การกล่าวถึงการทดสอบเกิดขึ้นบน Nexus 4 (mako / occam) ฉันขอแนะนำให้คุณใช้วิธีนี้เป็นการส่วนตัว แต่เนื่องจากฉันไม่สามารถหาวิธีที่เสถียรกว่านี้ได้ฉันจึงเชื่อว่านี่เป็นวิธีที่ดีที่สุด


วิธีการดั้งเดิม: ตั้งใจรหัสชุดหูฟัง

สำหรับช่วงเวลาที่คุณต้องชำระ

ส่วนต่อไปนี้ได้รับอิทธิพลอย่างมากจากคำตอบนี้โดยไรลีย์ C

วิธีการแสดงเจตนาของชุดหูฟังจำลองตามที่โพสต์ไว้ในคำถามเดิมดูเหมือนจะออกอากาศอย่างที่คาดหวัง แต่ดูเหมือนจะไม่บรรลุเป้าหมายในการรับสาย แม้ว่าจะมีรหัสที่ควรจัดการกับเจตนาเหล่านั้น แต่ก็ไม่ได้รับการดูแลซึ่งหมายความว่าจะต้องมีมาตรการตอบโต้ใหม่บางอย่างที่ใช้แทนวิธีนี้ บันทึกไม่ได้แสดงสิ่งที่น่าสนใจเช่นกันและโดยส่วนตัวฉันไม่เชื่อว่าการขุดผ่านซอร์ส Android สำหรับสิ่งนี้จะคุ้มค่าเพียงเพราะความเป็นไปได้ที่ Google จะแนะนำการเปลี่ยนแปลงเล็กน้อยที่ทำลายวิธีการที่ใช้ไปอย่างง่ายดาย

ตอนนี้มีอะไรที่เราทำได้ไหม?

ลักษณะการทำงานสามารถทำซ้ำได้อย่างสม่ำเสมอโดยใช้อินพุตที่เรียกใช้งานได้ มันต้องใช้เวลาในการโต้เถียง keycode สำหรับซึ่งเราก็ผ่านในKeyEvent.KEYCODE_HEADSETHOOK วิธีนี้ไม่จำเป็นต้องใช้การเข้าถึงรูททำให้เหมาะสำหรับกรณีการใช้งานทั่วไปในที่สาธารณะ แต่มีข้อเสียเล็กน้อยในวิธีนี้ - ไม่สามารถระบุเหตุการณ์การกดปุ่มชุดหูฟังเพื่อต้องการการอนุญาตได้ซึ่งหมายความว่ามันทำงานได้เหมือนจริง กดปุ่มและฟองขึ้นตลอดทั้งห่วงโซ่ซึ่งหมายความว่าคุณต้องระมัดระวังว่าเมื่อใดที่จะจำลองการกดปุ่มเท่าที่จะทำได้ตัวอย่างเช่นเรียกโปรแกรมเล่นเพลงให้เริ่มเล่นหากไม่มีใครที่มีลำดับความสำคัญสูงกว่าพร้อมที่จะจัดการ เหตุการณ์.

รหัส?

new Thread(new Runnable() {

    @Override
    public void run() {
        try {
            Runtime.getRuntime().exec("input keyevent " +
                    Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));
        } catch (IOException e) {
            // Runtime.exec(String) had an I/O problem, try to fall back
            String enforcedPerm = "android.permission.CALL_PRIVILEGED";
            Intent btnDown = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
                    Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN,
                            KeyEvent.KEYCODE_HEADSETHOOK));
            Intent btnUp = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
                    Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP,
                            KeyEvent.KEYCODE_HEADSETHOOK));

            mContext.sendOrderedBroadcast(btnDown, enforcedPerm);
            mContext.sendOrderedBroadcast(btnUp, enforcedPerm);
        }
    }

}).start();

tl; dr

มี API สาธารณะที่ดีสำหรับ Android 8.0 Oreo และใหม่กว่า

ไม่มี API สาธารณะก่อน Android 8.0 Oreo API ภายในไม่ จำกัด หรือไม่มีเอกสารประกอบ คุณควรดำเนินการด้วยความระมัดระวัง


เกี่ยวกับความตั้งใจของรหัสชุดหูฟังคุณได้ตรวจสอบแหล่งที่มาของ Google ที่นี่ด้วยเหตุผลใดที่พวกเขาจะหยุดดำเนินการ สิ่งที่ตลกคือการโทรยังคงสามารถปฏิเสธได้อย่างง่ายดายด้วยเจตนาเหล่านี้ (เพียงแค่เลียนแบบการกดแบบยาว) แต่ไม่มีอะไรที่จะตอบได้ ฉันยังไม่พบการตรวจสอบการอนุญาตอย่างชัดเจนหรือการบล็อกอื่น ๆ ที่เป็นไปได้และฉันหวังว่าชุดที่สองของตาอาจเปิดเผยบางสิ่ง
Riley C

ค่อนข้างยุ่งวุ่นวายจึงล่าช้า - จะพยายามใช้เวลาหาข้อมูลนี้ หลังจากดูอย่างรวดเร็วดูเหมือนว่า CallsManager จะสร้าง HeadsetMediaButton การโทรกลับเซสชันควรดูแลการเรียก handleHeadsetHook (KeyEvent) เมื่อมีการโทรกลับจาก MediaSessionManager ดูเหมือนว่าโค้ดทั้งหมดจะตรงกัน ... แต่ฉันสงสัยว่าอาจมีคนลบ KeyEvent.ACTION_DOWN ตั้งใจที่จะทดสอบได้หรือไม่ (นั่นคือยิง KeyEvent เท่านั้น ACTION_UP หนึ่งครั้งเดียว)
Valter Jansons

จริงๆแล้ว HeadsetMediaButton ทำงานร่วมกับ MediaSession และไม่ได้โต้ตอบกับ MediaSessionManager โดยตรง ...
Valter Jansons

1
สำหรับพวกคุณที่พบว่ามีสถานการณ์ที่ดูเหมือนว่าวิธีการดั้งเดิมใช้งานไม่ได้ (เช่น Lollipop มีปัญหา) ฉันมีข่าวดีและไม่ดี: ฉันได้จัดการเพื่อให้ ACTION_UP ทำงานได้ 100% ในรหัสของฉันด้วย a FULL_WAKE_LOCK จะไม่ทำงานกับ PARTIAL_WAKE_LOCK ไม่มีเอกสารใด ๆ เกี่ยวกับสาเหตุของเรื่องนี้ ฉันจะให้รายละเอียดในคำตอบในอนาคตเมื่อฉันทดสอบโค้ดการทดสอบของฉันอย่างครอบคลุมมากขึ้น แน่นอนว่าข่าวร้ายคือ FULL_WAKE_LOCK เลิกใช้งานแล้วดังนั้นนี่คือการแก้ไขที่จะคงอยู่ตราบเท่าที่ Google เก็บไว้ใน API
leRobot

1
การจับจากคำตอบเดิมไม่ได้ถูกเรียกในหลาย ๆ กรณี ฉันพบว่ามันดีกว่าที่จะโทรหาผู้บริหารก่อนแล้วจึงเรียกปุ่มขึ้นลงหลังจากนั้น
Warpzit

36

โซลูชันที่ใช้งานได้เต็มรูปแบบจะขึ้นอยู่กับรหัส @Valter Strods

เพื่อให้มันใช้งานได้คุณต้องแสดงกิจกรรม (ล่องหน) บนหน้าจอล็อกที่มีการเรียกใช้รหัส

AndroidManifest.xml

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />

<activity android:name="com.mysms.android.lib.activity.AcceptCallActivity"
        android:launchMode="singleTop"
        android:excludeFromRecents="true"
        android:taskAffinity=""
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:theme="@style/Mysms.Invisible">
    </activity>

กิจกรรมรับสาย

package com.mysms.android.lib.activity;

import android.app.Activity;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.WindowManager;

import org.apache.log4j.Logger;

import java.io.IOException;

public class AcceptCallActivity extends Activity {

     private static Logger logger = Logger.getLogger(AcceptCallActivity.class);

     private static final String MANUFACTURER_HTC = "HTC";

     private KeyguardManager keyguardManager;
     private AudioManager audioManager;
     private CallStateReceiver callStateReceiver;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);

         keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
         audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
     }

     @Override
     protected void onResume() {
         super.onResume();

         registerCallStateReceiver();
         updateWindowFlags();
         acceptCall();
     }

     @Override
     protected void onPause() {
         super.onPause();

         if (callStateReceiver != null) {
              unregisterReceiver(callStateReceiver);
              callStateReceiver = null;
         }
     }

     private void registerCallStateReceiver() {
         callStateReceiver = new CallStateReceiver();
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         registerReceiver(callStateReceiver, intentFilter);
     }

     private void updateWindowFlags() {
         if (keyguardManager.inKeyguardRestrictedInputMode()) {
              getWindow().addFlags(
                       WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
                                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
                                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
         } else {
              getWindow().clearFlags(
                       WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
                                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
         }
     }

     private void acceptCall() {

         // for HTC devices we need to broadcast a connected headset
         boolean broadcastConnected = MANUFACTURER_HTC.equalsIgnoreCase(Build.MANUFACTURER)
                  && !audioManager.isWiredHeadsetOn();

         if (broadcastConnected) {
              broadcastHeadsetConnected(false);
         }

         try {
              try {
                  logger.debug("execute input keycode headset hook");
                  Runtime.getRuntime().exec("input keyevent " +
                           Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));

              } catch (IOException e) {
                  // Runtime.exec(String) had an I/O problem, try to fall back
                  logger.debug("send keycode headset hook intents");
                  String enforcedPerm = "android.permission.CALL_PRIVILEGED";
                  Intent btnDown = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
                           Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN,
                                    KeyEvent.KEYCODE_HEADSETHOOK));
                  Intent btnUp = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
                           Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP,
                                    KeyEvent.KEYCODE_HEADSETHOOK));

                  sendOrderedBroadcast(btnDown, enforcedPerm);
                  sendOrderedBroadcast(btnUp, enforcedPerm);
              }
         } finally {
              if (broadcastConnected) {
                  broadcastHeadsetConnected(false);
              }
         }
     }

     private void broadcastHeadsetConnected(boolean connected) {
         Intent i = new Intent(Intent.ACTION_HEADSET_PLUG);
         i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         i.putExtra("state", connected ? 1 : 0);
         i.putExtra("name", "mysms");
         try {
              sendOrderedBroadcast(i, null);
         } catch (Exception e) {
         }
     }

     private class CallStateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
              finish();
         }
     }
}

สไตล์

<style name="Mysms.Invisible">
    <item name="android:windowFrame">@null</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowAnimationStyle">@null</item>
</style>

ในที่สุดก็เรียกมายากล!

Intent intent = new Intent(context, AcceptCallActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(intent);

1
โดยที่ mi suppoz ให้เพิ่มรหัสภายใต้ "ในที่สุดเรียกเวทมนตร์" จะใช้งานได้กับ Android 6.0 หรือไม่
Akshay Shah

ฉันมาที่นี่เพื่อบอกว่า BroadcastHeadsetConnected (เชื่อมต่อบูลีน) คือสิ่งที่แก้ไขปัญหาในอุปกรณ์ Samsung A3 2016 หากไม่มีวิธีนั้นวิธีการที่คล้ายกันมาก (โดยใช้กิจกรรมที่แยกจากกันโปร่งใสและเธรดสำหรับการเรียกใช้และอะไรก็ตามที่ไม่ได้) ก็ทำงานได้เต็มที่สำหรับอุปกรณ์ที่ผ่านการทดสอบประมาณ 20 เครื่องจากนั้น A3 นี้ก็เข้ามาและบังคับให้ฉันตรวจสอบคำถามนี้อีกครั้งเพื่อหาคำตอบใหม่ หลังจากเปรียบเทียบกับรหัสของฉันนั่นคือความแตกต่างที่สำคัญ!
leRobot

1
ฉันจะปฏิเสธสายได้อย่างไร? คุณสามารถอัปเดตคำตอบเพื่อแสดงสิ่งนี้ได้หรือไม่
Amanni

@leRobot คำตอบนี้ตรวจสอบว่าเป็นอุปกรณ์ HTC สำหรับ BroadcastHeadsetConnected หรือไม่คุณจะตรวจสอบได้อย่างไรว่าเป็นอุปกรณ์ Samsung A3 2016 หรือไม่? นี่เป็นคำตอบที่ดีจริงๆแอปของฉันสามารถรับสายได้แม้หน้าจอจะล็อก
eepty

@eepty คุณสามารถใช้การอ้างอิงอุปกรณ์อย่างเป็นทางการสำหรับสร้างข้อมูล ( support.google.com/googleplay/answer/1727131?hl=th ) ฉันใช้ Build.MODEL.startsWith ("SM-A310") สำหรับ A3 2016 แต่! ฉันสามารถยืนยันได้ว่า A3 2016 ไม่รองรับการออกอากาศที่เชื่อมต่อกับชุดหูฟัง! สิ่งที่แก้ไขปัญหาของฉันได้จริงคือการเปลี่ยนลำดับเพื่อให้ Runtime.getRuntime (). exec (... ทริกเกอร์ก่อนสำหรับอุปกรณ์เหล่านี้ดูเหมือนว่าจะทำงานทุกครั้งสำหรับอุปกรณ์นั้นและไม่ถอยกลับไปสู่ข้อยกเว้น
leRobot

14

ต่อไปนี้เป็นแนวทางอื่นที่เหมาะกับฉัน ส่งเหตุการณ์สำคัญไปยังเซิร์ฟเวอร์โทรคมนาคมโดยตรงโดยใช้ MediaController API สิ่งนี้ต้องการให้แอปมีสิทธิ์BIND_NOTIFICATION_LISTENER_SERVICE และได้รับอนุญาตอย่างชัดเจนในการเข้าถึงการแจ้งเตือนจากผู้ใช้:

@TargetApi(Build.VERSION_CODES.LOLLIPOP) 
void sendHeadsetHookLollipop() {
    MediaSessionManager mediaSessionManager =  (MediaSessionManager) getApplicationContext().getSystemService(Context.MEDIA_SESSION_SERVICE);

    try {
        List<MediaController> mediaControllerList = mediaSessionManager.getActiveSessions 
                     (new ComponentName(getApplicationContext(), NotificationReceiverService.class));

        for (MediaController m : mediaControllerList) {
             if ("com.android.server.telecom".equals(m.getPackageName())) {
                 m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
                 log.info("HEADSETHOOK sent to telecom server");
                 break;
             }
        }
    } catch (SecurityException e) {
        log.error("Permission error. Access to notification not granted to the app.");      
    }  
}

NotificationReceiverService.class ในโค้ดด้านบนอาจเป็นเพียงคลาสว่างเปล่า

import android.service.notification.NotificationListenerService;

public class NotificationReceiverService extends NotificationListenerService{
     public NotificationReceiverService() {
     }
}

ด้วยส่วนที่เกี่ยวข้องในรายการ:

    <service android:name=".NotificationReceiverService" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
        android:enabled="true" android:exported="true">
    <intent-filter>
         <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>

เนื่องจากเป้าหมายของเหตุการณ์มีความชัดเจนจึงควรหลีกเลี่ยงผลข้างเคียงใด ๆ จากการเรียกใช้โปรแกรมเล่นสื่อ

หมายเหตุ: เซิร์ฟเวอร์โทรคมนาคมอาจไม่สามารถใช้งานได้ทันทีหลังจากเหตุการณ์ดัง เพื่อให้สิ่งนี้ทำงานได้อย่างน่าเชื่อถือแอปอาจมีประโยชน์ในการใช้MediaSessionManager OnActiveSessionsChangedListenerเพื่อตรวจสอบว่าเซิร์ฟเวอร์โทรคมนาคมเปิดใช้งานเมื่อใดก่อนที่จะส่งเหตุการณ์

อัปเดต:

ในAndroid Oต้องจำลองACTION_DOWNก่อนACTION_UPมิฉะนั้นข้างต้นจะไม่มีผล เช่นสิ่งต่อไปนี้เป็นสิ่งจำเป็น:

m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));

แต่เนื่องจากมีการโทรอย่างเป็นทางการสำหรับการรับสายตั้งแต่ Android O (ดูคำตอบด้านบน) จึงอาจไม่จำเป็นต้องใช้แฮ็คนี้อีกต่อไปเว้นแต่จะติดอยู่กับระดับ API คอมไพล์เก่าก่อน Android O


มันไม่ได้ผลสำหรับฉัน ส่งกลับข้อผิดพลาดการอนุญาต ไม่ได้ให้สิทธิ์การเข้าถึงการแจ้งเตือนแก่แอป ฉันใช้ Android L
Jame

2
ขั้นตอนนี้ต้องมีขั้นตอนเพิ่มเติมในการให้สิทธิ์อย่างชัดเจนโดยผู้ใช้บางแห่งในเมนูการตั้งค่าขึ้นอยู่กับระบบนอกเหนือจากการยอมรับการอนุญาตในรายการ
headuck

การรายงานสิ่งนี้ดูเหมือนจะใช้ได้ผลในบางกรณี: Galaxy A3 2016 พร้อม Marshmallow ฉันจะทดสอบสิ่งนี้ในกลุ่มของอุปกรณ์ A3 ที่ไม่ทำงานกับวิธีการป้อนข้อมูลคีย์เหตุการณ์เนื่องจากข้อยกเว้น FATAL: java.lang.SecurityException: การแทรกไปยังแอปพลิเคชันอื่นต้องได้รับอนุญาต INJECT_EVENTS อุปกรณ์ที่กระทำผิดเป็นฐานผู้ใช้ของฉันประมาณ 2% และฉันไม่ได้จำลองข้อยกเว้น แต่จะลองใช้วิธีนี้เพื่อดูว่าพวกเขาสามารถรับสายได้หรือไม่ โชคดีที่แอปของฉันกำลังขอการแจ้งเตือนที่ชัดเจนอยู่แล้ว เข้าถึงเพื่อวัตถุประสงค์อื่น
leRobot

หลังจากการทดสอบอย่างละเอียดฉันดีใจที่ได้รายงานว่าอุปกรณ์ A3 2016 ที่ล้มเหลวด้วยวิธี "input keyevent" ของ exec สามารถทำงานร่วมกับเมธอด MediaController # dispatchMediaButtonEvent (<hook KeryEvent>)) ได้ เห็นได้ชัดว่าสิ่งนี้ใช้งานได้เฉพาะหลังจากที่ผู้ใช้อนุญาตการเข้าถึงการแจ้งเตือนอย่างชัดเจนดังนั้นคุณจะต้องเพิ่มหน้าจอที่นำทางไปยังการตั้งค่า Android สำหรับสิ่งนั้นและโดยพื้นฐานแล้วคุณต้องให้ผู้ใช้ดำเนินการเพิ่มเติมสำหรับสิ่งนี้ตามรายละเอียดในคำตอบ ในแอปของฉันเราได้ดำเนินการตามขั้นตอนเพิ่มเติมเพื่อถามสิ่งนี้หากผู้ใช้ไปที่หน้าจอนั้น แต่ไม่ได้เพิ่มการแจ้ง เข้าถึง
leRobot

สิ่งนี้ใช้ได้กับ Android Nougat วิธีแก้ปัญหาของ @notz ใช้งานได้ดี แต่บ่นว่า "มีเพียงระบบเท่านั้นที่สามารถส่งเหตุการณ์สำคัญของสื่อไปยังเซสชันที่มีลำดับความสำคัญระดับโลก" บน Android 7
PB

9

ทำอย่างละเอียดเล็กน้อยกับคำตอบโดย @Muzikant และการปรับเปลี่ยนมันเล็กน้อยในการทำงานทำความสะอาดบิตบนอุปกรณ์ของฉันพยายามที่input keyevent 79คงที่สำหรับKeyEvent.KEYCODE_HEADSETHOOK คร่าวๆมาก :

    new Thread(new Runnable() {

        @Override
        public void run() {

            try {

                Runtime.getRuntime().exec( "input keyevent " + KeyEvent.KEYCODE_HEADSETHOOK );
            }
            catch (Throwable t) {

                // do something proper here.
            }
        }
    }).start();

ยกโทษให้กับรูปแบบการเขียนโค้ดที่ค่อนข้างแย่ฉันไม่ค่อยเชี่ยวชาญในการเรียก Runtime.exec () โปรดทราบว่าอุปกรณ์ของฉันไม่ได้รูทและฉันไม่ได้ขอสิทธิ์ระดับรูท

ปัญหาเกี่ยวกับแนวทางนี้คือใช้ได้ภายใต้เงื่อนไขบางประการเท่านั้น (สำหรับฉัน) นั่นคือถ้าฉันเรียกใช้เธรดด้านบนจากตัวเลือกเมนูที่ผู้ใช้เลือกในขณะที่มีสายเรียกเข้าการโทรก็รับสายได้ดี หากฉันเรียกใช้จากเครื่องรับที่ตรวจสอบสถานะสายเรียกเข้าเครื่องจะถูกเพิกเฉยโดยสิ้นเชิง

ดังนั้นใน Nexus 5 ของฉันจึงทำงานได้ดีสำหรับการตอบรับที่ผู้ใช้เป็นผู้กำหนดและควรเหมาะกับวัตถุประสงค์ของหน้าจอการโทรที่กำหนดเอง มันใช้ไม่ได้กับแอปพลิเคชันประเภทควบคุมการโทรอัตโนมัติทุกประเภท

นอกจากนี้โปรดทราบว่าข้อควรระวังทั้งหมดที่เป็นไปได้รวมถึงสิ่งนี้ด้วยเช่นกันอาจจะหยุดทำงานในการอัปเดตหนึ่งหรือสองรายการ


input keyevent 79ทำงานได้ดีบน Sony Xperia 5.0 ทำงานเมื่อโทรจากกิจกรรมหรือจากเครื่องรับสัญญาณออกอากาศ
nicolas

0

ผ่านคำสั่ง adb วิธีรับสายโดย adb

โปรดทราบว่า Android เป็น Linux ที่มี JVM ขนาดใหญ่ที่ส่วนหน้า คุณสามารถดาวน์โหลดแอปพลิเคชันบรรทัดคำสั่งและรูทโทรศัพท์และตอนนี้คุณมีคอมพิวเตอร์ Linux และบรรทัดคำสั่งปกติที่ทำสิ่งปกติ เรียกใช้สคริปต์คุณสามารถ ssh ได้ด้วย (เคล็ดลับ OpenVPN)


0

ขอบคุณ @notz คำตอบของการทำงานสำหรับฉันใน Lolillop เพื่อให้รหัสนี้ใช้งานได้กับ Android SDK รุ่นเก่าคุณสามารถทำรหัสนี้ได้:

if (Build.VERSION.SDK_INT >= 21) {  
    Intent answerCalintent = new Intent(context, AcceptCallActivity.class);  
    answerCalintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
                             Intent.FLAG_ACTIVITY_CLEAR_TASK  | 
                             Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    context.startActivity(answerCalintent);  
}  
else {  
  if (telephonyService != null) {  
    try {  
        telephonyService.answerRingingCall();  
    }  
    catch (Exception e) {  
        answerPhoneHeadsethook();  
    }  
  }  
}  

0

วิธีเปิด Speaker Phone หลังจากรับสายโดยอัตโนมัติ

ฉันแก้ปัญหาข้างต้นด้วย setSpeakerphoneOn ฉันคิดว่ามันคุ้มค่าที่จะโพสต์ที่นี่เนื่องจากกรณีการใช้งานสำหรับการรับสายอัตโนมัติมักจะต้องใช้สปีกเกอร์โฟนเพื่อประโยชน์ ขอบคุณอีกครั้งสำหรับทุกคนในกระทู้นี้ช่างยอดเยี่ยมมาก

สิ่งนี้ใช้ได้กับฉันบน Android 5.1.1 บน Nexus 4 โดยไม่ต้อง ROOT ;)

ต้องได้รับอนุญาต:

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

รหัส Java:

// this means the phone has answered
if(state==TelephonyManager.CALL_STATE_OFFHOOK)
{
    // try and turn on speaker phone
    final Handler mHandler = new Handler();
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            AudioManager audioManager = (AudioManager) localContext.getSystemService(Context.AUDIO_SERVICE);

            // this doesnt work without android.permission.MODIFY_PHONE_STATE
            // audioManager.setMode(AudioManager.MODE_IN_CALL);

            // weirdly this works
            audioManager.setMode(AudioManager.MODE_NORMAL); // this is important
            audioManager.setSpeakerphoneOn(true);

            // note the phone interface won't show speaker phone is enabled
            // but the phone speaker will be on
            // remember to turn it back off when your done ;)
        }
    }, 500); // half a second delay is important or it might fail
}

1
น่าสนใจ. ฉันกำลังพยายามรับสายและเปิดลำโพงด้วยกันดังนั้นวิธีนี้จึงดูเหมือนจะแก้ปัญหาได้ทั้งคู่ :) ฉันมีคำถามที่คล้ายกันกับความคิดเห็นบางส่วนในคำตอบอื่น ๆ : รหัสนี้ไปไหน?
fangmobile

-1

รันคำสั่งต่อไปนี้ในฐานะ root:

input keyevent 5

รายละเอียดเพิ่มเติมเกี่ยวกับการจำลอง keyevents ที่นี่

คุณสามารถใช้คลาสพื้นฐานที่ฉันสร้างขึ้นเพื่อเรียกใช้คำสั่งเป็นรูทจากแอปของคุณ


1
ในขณะที่ทดสอบกับโปรไฟล์ผู้ใช้ทั่วไปสิ่งนี้ทำให้ฉันใช้ UI ในการโทรโดยขอให้ฉันปัดไปทางซ้าย / ขวาเพื่อปฏิเสธ / รับสายหรือใช้การดำเนินการ / ตอบกลับ หาก OP กำลังสร้างหน้าจอสายเรียกเข้าที่กำหนดเองนี่ไม่ได้ช่วยอะไรเลยเว้นแต่ว่ามันจะทำงานแตกต่างกันภายใต้รูทซึ่งฉันสงสัยว่ามันทำงานได้ไม่ดีสำหรับผู้ใช้ทั่วไปการโทรอาจล้มเหลวและไม่กระตุ้นการกระทำที่แตกต่างกัน
Valter Jansons

-2

ทดสอบสิ่งนี้: ขั้นแรกเพิ่มสิทธิ์จากนั้นใช้ killCall () เพื่อวางสายโดยใช้ answerCall () เพื่อรับสาย

<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"></uses-permission>


public void killCall() {
    try {
        TelephonyManager telephonyManager =
                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);

        Class classTelephony = Class.forName(telephonyManager.getClass().getName());
        Method methodGetITelephony = classTelephony.getDeclaredMethod("getITelephony");

        methodGetITelephony.setAccessible(true);

        Object telephonyInterface = methodGetITelephony.invoke(telephonyManager);

        Class telephonyInterfaceClass =
                Class.forName(telephonyInterface.getClass().getName());
        Method methodEndCall = telephonyInterfaceClass.getDeclaredMethod("endCall");

        methodEndCall.invoke(telephonyInterface);

    } catch (Exception ex) {
        Log.d(TAG, "PhoneStateReceiver **" + ex.toString());
    }
}

public void answerCall() {
    try {
        Runtime.getRuntime().exec("input keyevent " +
                Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));

    } catch (IOException e) {
        answerRingingCallWithIntent();
    }
}

public void answerRingingCallWithIntent() {
    try {
        Intent localIntent1 = new Intent(Intent.ACTION_HEADSET_PLUG);
        localIntent1.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        localIntent1.putExtra("state", 1);
        localIntent1.putExtra("microphone", 1);
        localIntent1.putExtra("name", "Headset");
        getContext().sendOrderedBroadcast(localIntent1, "android.permission.CALL_PRIVILEGED");

        Intent localIntent2 = new Intent(Intent.ACTION_MEDIA_BUTTON);
        KeyEvent localKeyEvent1 = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK);
        localIntent2.putExtra(Intent.EXTRA_KEY_EVENT, localKeyEvent1);
        getContext().sendOrderedBroadcast(localIntent2, "android.permission.CALL_PRIVILEGED");

        Intent localIntent3 = new Intent(Intent.ACTION_MEDIA_BUTTON);
        KeyEvent localKeyEvent2 = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK);
        localIntent3.putExtra(Intent.EXTRA_KEY_EVENT, localKeyEvent2);
        getContext().sendOrderedBroadcast(localIntent3, "android.permission.CALL_PRIVILEGED");

        Intent localIntent4 = new Intent(Intent.ACTION_HEADSET_PLUG);
        localIntent4.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        localIntent4.putExtra("state", 0);
        localIntent4.putExtra("microphone", 1);
        localIntent4.putExtra("name", "Headset");
        getContext().sendOrderedBroadcast(localIntent4, "android.permission.CALL_PRIVILEGED");
    } catch (Exception e2) {
        e2.printStackTrace();
    }
}

-2

FYI ถ้าคุณกำลังสนใจในวิธีการที่จะวางสายอย่างต่อเนื่องบน Android O, Valter ของการทำงานถ้าคุณเปลี่ยนวิธีการที่คุณเรียกว่าเป็นที่Method 1: TelephonyManager.answerRingingCall()endCall

ต้องandroid.permission.CALL_PHONEได้รับอนุญาตเท่านั้น

นี่คือรหัส:

// set the logging tag constant; you probably want to change this
final String LOG_TAG = "TelephonyAnswer";

public void endCall() {
    TelephonyManager tm = (TelephonyManager) mContext
            .getSystemService(Context.TELEPHONY_SERVICE);

    try {
        if (tm == null) {
            // this will be easier for debugging later on
            throw new NullPointerException("tm == null");
        }

        // do reflection magic
        tm.getClass().getMethod("endCall").invoke(tm);
    } catch (Exception e) {
        // we catch it all as the following things could happen:
        // NoSuchMethodException, if the answerRingingCall() is missing
        // SecurityException, if the security manager is not happy
        // IllegalAccessException, if the method is not accessible
        // IllegalArgumentException, if the method expected other arguments
        // InvocationTargetException, if the method threw itself
        // NullPointerException, if something was a null value along the way
        // ExceptionInInitializerError, if initialization failed
        // something more crazy, if anything else breaks

        // TODO decide how to handle this state
        // you probably want to set some failure state/go to fallback
        Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.