Cập nhật với Android 8.0 Oreo
Mặc dù câu hỏi ban đầu được đặt ra là hỗ trợ Android L, nhưng mọi người dường như vẫn đang đánh vào câu hỏi và câu trả lời này, vì vậy cần mô tả những cải tiến được giới thiệu trong Android 8.0 Oreo. Các phương pháp tương thích ngược vẫn được mô tả bên dưới.
Những gì đã thay đổi?
Bắt đầu với Android 8.0 Oreo , nhóm quyền PHONE cũng chứa quyền ANSWER_PHONE_CALLS . Như tên của quyền cho thấy, việc giữ nó cho phép ứng dụng của bạn chấp nhận các cuộc gọi đến theo chương trình thông qua một lệnh gọi API thích hợp mà không có bất kỳ hành vi tấn công nào xung quanh hệ thống bằng cách sử dụng phản chiếu hoặc mô phỏng người dùng.
Làm thế nào để chúng tôi sử dụng thay đổi này?
Bạn nên kiểm tra phiên bản hệ thống trong thời gian chạy nếu bạn đang hỗ trợ các phiên bản Android cũ hơn để có thể đóng gói lệnh gọi API mới này trong khi vẫn duy trì hỗ trợ cho các phiên bản Android cũ hơn đó. Bạn nên tuân theo yêu cầu quyền tại thời điểm chạy để có được quyền mới đó trong thời gian chạy, như tiêu chuẩn trên các phiên bản Android mới hơn.
Sau khi có được quyền, ứng dụng của bạn chỉ cần gọi phương thức acceptRingingCall của TelecomManager . Một lời gọi cơ bản trông như sau:
TelecomManager tm = (TelecomManager) mContext
.getSystemService(Context.TELECOM_SERVICE);
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.acceptRingingCall();
Phương pháp 1: TelephonyManager.answerRingingCall ()
Đối với khi bạn có quyền kiểm soát không giới hạn đối với thiết bị.
Cái này là cái gì?
Có TelephonyManager.answerRingingCall () là một phương thức ẩn, nội bộ. Nó hoạt động như một cầu nối cho ITelephony.answerRingingCall () đã được thảo luận trên các mạng interwebs và có vẻ đầy hứa hẹn khi bắt đầu. Nó không có sẵn trên 4.4.2_r1 vì nó chỉ được giới thiệu trong cam kết 83da75d cho Android 4.4 KitKat ( dòng 1537 trên 4.4.3_r1 ) và sau đó được "giới thiệu lại" trong cam kết f1e1e77 cho Lollipop ( dòng 3138 trên 5.0.0_r1 ) do cách Git tree đã được cấu trúc. Điều này có nghĩa là trừ khi bạn chỉ hỗ trợ các thiết bị có Lollipop, đây có thể là một quyết định tồi dựa trên thị phần nhỏ của nó tính đến thời điểm hiện tại, bạn vẫn cần cung cấp các phương pháp dự phòng nếu đi theo con đường này.
Làm thế nào chúng ta sẽ sử dụng điều này?
Vì phương pháp được đề cập bị ẩn khỏi việc sử dụng các ứng dụng SDK, bạn cần sử dụng phản chiếu để kiểm tra động và sử dụng phương pháp trong thời gian chạy. Nếu bạn không quen thuộc với phản xạ, bạn có thể đọc nhanh Phản ánh là gì, và tại sao nó lại hữu ích? . Bạn cũng có thể tìm hiểu sâu hơn về các chi tiết cụ thể tại Trail: API Reflection nếu bạn muốn làm như vậy.
Và nó trông như thế nào trong mã?
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);
}
Điều này là quá tốt là đúng!
Trên thực tế, có một vấn đề nhỏ. Phương thức này phải có đầy đủ chức năng, nhưng người quản lý bảo mật muốn người gọi giữ android.permission.MODIFY_PHONE_STATE . Quyền này chỉ nằm trong lĩnh vực của các tính năng được ghi lại một phần của hệ thống vì các bên thứ 3 không được phép chạm vào nó (như bạn có thể thấy từ tài liệu cho nó). Bạn có thể thử thêm một <uses-permission>
cho nó nhưng điều đó sẽ không hiệu quả vì mức độ bảo vệ cho quyền này là hệ thống chữ ký | ( xem dòng 1201 của core / AndroidManifest trên 5.0.0_r1 ).
Bạn có thể đọc Vấn đề 34785: Cập nhật tài liệu android: protectionLevel được tạo vào năm 2012 để thấy rằng chúng tôi còn thiếu thông tin chi tiết về "cú pháp ống" cụ thể, nhưng từ thử nghiệm xung quanh, nó có vẻ như nó phải hoạt động như một 'VÀ' nghĩa là tất cả các cờ cụ thể phải được thực hiện để được cấp quyền. Làm việc theo giả định đó, có nghĩa là bạn phải có ứng dụng của mình:
Được cài đặt như một ứng dụng hệ thống.
Điều này sẽ ổn và có thể được thực hiện bằng cách yêu cầu người dùng cài đặt bằng cách sử dụng ZIP trong khôi phục, chẳng hạn như khi root hoặc cài đặt ứng dụng Google trên ROM tùy chỉnh chưa được đóng gói sẵn.
Được ký bằng chữ ký giống như các khung / cơ sở hay còn gọi là hệ thống, hay còn gọi là ROM.
Đây là nơi mà các vấn đề xuất hiện. Để làm được điều này, bạn cần nắm chắc các phím được sử dụng để ký khung / cơ sở. Bạn không chỉ phải có quyền truy cập vào các khóa của Google dành cho hình ảnh xuất xưởng của Nexus mà còn phải có quyền truy cập vào tất cả các khóa của nhà phát triển ROM và OEM khác. Điều này có vẻ không hợp lý vì vậy bạn có thể ký ứng dụng của mình bằng các khóa hệ thống bằng cách tạo một ROM tùy chỉnh và yêu cầu người dùng của bạn chuyển sang nó (điều này có thể khó) hoặc bằng cách tìm một cách khai thác mà mức độ bảo vệ quyền có thể bị bỏ qua (cũng có thể khó).
Ngoài ra, hành vi này dường như liên quan đến Vấn đề 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS không còn hoạt động sử dụng cùng mức bảo vệ cùng với cờ phát triển không có tài liệu.
Làm việc với TelephonyManager nghe có vẻ tốt, nhưng sẽ không hoạt động trừ khi bạn nhận được sự cho phép thích hợp, điều không dễ thực hiện trong thực tế.
Điều gì về việc sử dụng TelephonyManager theo những cách khác?
Đáng buồn thay, có vẻ như yêu cầu bạn phải giữ android.permission.MODIFY_PHONE_STATE để sử dụng các công cụ thú vị, điều này có nghĩa là bạn sẽ gặp khó khăn khi truy cập vào các phương pháp đó.
Cách 2: gọi dịch vụ MÃ DỊCH VỤ
Để biết khi nào bạn có thể kiểm tra rằng bản dựng đang chạy trên thiết bị sẽ hoạt động với mã được chỉ định.
Nếu không có khả năng tương tác với TelephonyManager, cũng có khả năng tương tác với dịch vụ thông qua service
tệp thực thi.
Cái này hoạt động ra sao?
Nó khá đơn giản, nhưng thậm chí có ít tài liệu hơn về tuyến đường này so với những tuyến đường khác. Chúng tôi biết chắc chắn tệp thực thi có hai đối số - tên dịch vụ và mã.
Tên dịch vụ chúng tôi muốn sử dụng là điện thoại .
Điều này có thể được nhìn thấy bằng cách chạy service list
.
Các đang chúng ta muốn sử dụng dường như đã được 6 nhưng dường như đến nay được 5 .
Có vẻ như nó đã được dựa trên IBinder.FIRST_CALL_TRANSACTION + 5 cho nhiều phiên bản hiện nay (từ 1.5_r4 đến 4.4.4_r1 ) nhưng trong quá trình thử nghiệm cục bộ, mã 5 đã hoạt động để trả lời cuộc gọi đến. Vì Lollipo là một bản cập nhật lớn, nên có thể hiểu được nội dung bên trong cũng thay đổi ở đây.
Điều này dẫn đến một lệnh của service call phone 5
.
Làm thế nào để chúng tôi sử dụng điều này theo chương trình?
Java
Đoạn mã sau là một triển khai thô được thực hiện để hoạt động như một bằng chứng về khái niệm. Nếu bạn thực sự muốn tiếp tục và sử dụng phương pháp này, bạn có thể muốn kiểm tra hướng dẫn sử dụng su không gặp sự cố và có thể chuyển sang libsuperuser được phát triển đầy đủ hơn bởi 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();
}
Rõ ràng
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
Điều này có thực sự yêu cầu quyền truy cập root không?
Đáng buồn thay, nó có vẻ như vậy. Bạn có thể thử sử dụng Runtime.exec trên đó, nhưng tôi không thể gặp may với lộ trình đó.
Làm thế nào ổn định là điều này?
Tôi rất vui vì bạn đã hỏi. Do không được ghi chép lại, điều này có thể xảy ra trên nhiều phiên bản khác nhau, như được minh họa bằng sự khác biệt về mã có vẻ ở trên. Tên dịch vụ có lẽ nên ở trên điện thoại trên các bản dựng khác nhau, nhưng đối với tất cả những gì chúng ta biết, giá trị mã có thể thay đổi trên nhiều bản dựng của cùng một phiên bản (sửa đổi nội bộ bằng cách ví dụ, giao diện của OEM) lần lượt phá vỡ phương pháp được sử dụng. Do đó, điều đáng nói là thử nghiệm diễn ra trên Nexus 4 (mako / Occam). Cá nhân tôi khuyên bạn không nên sử dụng phương pháp này, nhưng vì tôi không thể tìm được phương pháp ổn định hơn, tôi tin rằng đây là cách tốt nhất.
Phương pháp ban đầu: Ý định mã khóa tai nghe
Cho những lúc bạn phải giải quyết.
Phần sau bị ảnh hưởng mạnh mẽ bởi câu trả lời này bởi Riley C .
Phương pháp ý định tai nghe mô phỏng như được đăng trong câu hỏi ban đầu dường như được phát sóng giống như người ta mong đợi, nhưng nó dường như không đạt được mục tiêu trả lời cuộc gọi. Mặc dù dường như có mã sẵn sàng xử lý những ý định đó, nhưng chúng chỉ đơn giản là không được quan tâm, điều đó có nghĩa là phải có một số loại biện pháp đối phó mới để chống lại phương pháp này. Nhật ký cũng không hiển thị bất kỳ điều gì đáng quan tâm và cá nhân tôi không tin rằng việc đào qua nguồn Android cho điều này sẽ đáng giá chỉ do khả năng Google giới thiệu một thay đổi nhỏ dễ dàng phá vỡ phương pháp được sử dụng.
Có điều gì chúng ta có thể làm ngay bây giờ không?
Hành vi có thể được tái tạo nhất quán bằng cách sử dụng tệp thực thi đầu vào. Nó có một đối số mã khóa, mà chúng ta chỉ cần truyền vào KeyEvent.KEYCODE_HEADSETHOOK . Phương pháp này thậm chí không yêu cầu quyền truy cập root làm cho nó phù hợp với các trường hợp sử dụng phổ biến trong công chúng, nhưng có một nhược điểm nhỏ trong phương pháp - sự kiện nhấn nút tai nghe không thể được chỉ định để yêu cầu sự cho phép, có nghĩa là nó hoạt động như thật nhấn nút và bong bóng lên trong toàn bộ chuỗi, điều này có nghĩa là bạn phải thận trọng về thời điểm mô phỏng thao tác nhấn nút có thể, ví dụ: kích hoạt trình phát nhạc bắt đầu phát lại nếu không có ai khác có mức độ ưu tiên cao hơn sẵn sàng xử lý sự kiện.
Mã?
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
Có một API công khai tuyệt vời cho Android 8.0 Oreo trở lên.
Không có API công khai trước Android 8.0 Oreo. Các API nội bộ nằm ngoài giới hạn hoặc đơn giản là không có tài liệu. Bạn nên tiến hành một cách thận trọng.