อัปเดตด้วย 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) {
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หากคุณสนใจที่จะทำเช่นนั้น
และรหัสนั้นมีลักษณะอย่างไร?
final String LOG_TAG = "TelephonyAnswer";
TelephonyManager tm = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
try {
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.getClass().getMethod("answerRingingCall").invoke(tm);
} catch (Exception e) {
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" ซึ่งหมายถึง แฟล็กที่ระบุต้องได้รับการตอบสนองเพื่อการอนุญาตที่จะได้รับ การทำงานภายใต้สมมติฐานนั้นหมายความว่าคุณต้องมีใบสมัครของคุณ:
ติดตั้งเป็นแอปพลิเคชันระบบ
สิ่งนี้ควรจะใช้ได้ดีและสามารถทำได้โดยขอให้ผู้ใช้ติดตั้งโดยใช้ ZIP ในการกู้คืนเช่นเมื่อรูทหรือติดตั้งแอพ Google บน ROM แบบกำหนดเองที่ยังไม่มีแพ็กเกจ
ลงนามด้วยลายเซ็นเดียวกันกับเฟรมเวิร์ก / ฐานหรือที่เรียกว่าระบบหรือที่เรียกว่า 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) {
}
} catch (IOException e) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
ประจักษ์
<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) {
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 ภายในไม่ จำกัด หรือไม่มีเอกสารประกอบ คุณควรดำเนินการด้วยความระมัดระวัง