Дано: есть приложение Ultimate Call Screen HD - аналогичных приложений на маркете довольно много.
Вопрос: как это работает? Как написано?
У кого какие идеи?
P.S. Ориентируемся на нерутованные аппараты.
Ответ
TL;DR
Приложение представляется системе гарнитурой для того, чтобы принимать/завершать звонки
У приложения Ultimate Call Screen HD в манифесте указано следущее:
Из манифеста видно, что PhoneReceiver получает первым интенты от системы про исходящий/входящий звонок. Далее он делегирует обработку сервисам InCallService и OutcallService, которые отображают необходимый интерфейс для управления и используют AudioManager для передачи голоса
UPDATE:
Откроем класс InCallService
public class InCallService extends Service implements SensorEventListener, a, b, e {
...
static boolean H;
WindowManager G;
WindowManager.LayoutParams I;
CallWindowView J;
...
private void e(final boolean b) {
if (b) {
return;
}
this.G.addView((View)this.J, (ViewGroup$LayoutParams)this.I);
InCallService.H = true;
}
private void f(final boolean b) {
if (!b) {
return;
}
if (this.J.getWindowToken() != null) {
this.G.removeView((View)this.J);
}
InCallService.H = false;
}
private void t() {
this.I = new WindowManager.LayoutParams(-1, -1, 2010, 2621600, -1);
if (g.a("hide_status_bar", true)) {
final WindowManager.LayoutParams i = this.I;
i.flags |= 0x100;
}
else {
this.I.type = 2003;
}
this.I.gravity = 80;
if (g.a("force_full_brightness", true)) {
this.I.screenBrightness = 1.0f;
}
(this.J = (CallWindowView)((LayoutInflater)this.getSystemService("layout_inflater")).inflate(2130903099, (ViewGroup)null)).a(new a(this));
this.G = (WindowManager)this.getSystemService("window");
}
...
}
Из этого кода видно, что приложение накрывает стандартное окно звонилки своим CallWindowView (для этого и необходимо разрешение android.permission.SYSTEM_ALERT_WINDOW). Разметка для входящего звонка хранится в R.layout.two_button_frame = 2130903099
R.id.answerbutton = 2131558590
R.id.rejectbutton = 2131558495
В сервисе есть код:
public void onCreate() {
...
this.t();
this.at = (AudioManager)this.al.getSystemService("audio");
...
this.f = (Button)this.J.findViewById(2131558495);
this.e = (Button)this.J.findViewById(2131558590);
...
this.J.setOnTouchListener((View$OnTouchListener)new ac(this));
this.J.setOnKeyListener((View$OnKeyListener)new ad(this));
this.f.setOnClickListener((View$OnClickListener)new ae(this));
this.f.setOnLongClickListener((View$OnLongClickListener)new af(this));
this.e.setOnClickListener((View$OnClickListener)new b(this));
this.e.setOnLongClickListener((View$OnLongClickListener)new c(this));
...
}
При нажатии кнопки "Принять вызов" срабатывает слушатель:
class b implements View$OnClickListener {
final InCallService a;
b(final InCallService a) {
super();
this.a = a;
}
public void onClick(final View view) {
...
if (this.a.a(this.a.al)) {
this.a.ak.a(this.a.al);
...
}
else {
final Intent intent = new Intent("android.intent.action.VIEW"); intent.setData(Uri.parse("market://details?id=com.lowveld.ucshdlicense"));
intent.setFlags(268435456);
this.a.startActivity(intent);
this.a.d();
}
...
}
}
Который передает управление в данный класс:
public class a {
Boolean a;
Boolean b;
public a() {
super();
this.a = false;
this.b = false;
}
private void a(final Context context, final int n, final boolean b) {
new Thread(new b(this, n, b, context)).start();
}
private void a(final Context context, final boolean b) {
this.a = g.a("isHeadsetOn", false);
this.b = ((AudioManager)context.getSystemService("audio")).isWiredHeadsetOn();
while (true) {
Label_0065: {
if (!g.a("root_activate_answer", false)) { //можно ли использовать рут-права
break Label_0065;
}
try {
this.b();
final int n = 0;
if (n != 0) {
this.b(context, b); //рут-права отсутствуют/нельзя использовать
}
return;
}
catch (Exception ex) {
final int n = 1;
continue;
}
}
final int n = 1;
continue;
}
}
private void b(final Context context, final boolean b) {
if (context != null) {
if (!j.b()) { API < 16
final Intent intent = new Intent("android.intent.action.HEADSET_PLUG");
intent.addFlags(1073741824);
intent.putExtra("state", 2);
intent.putExtra("name", "Headset");
context.sendOrderedBroadcast(intent, (String)null);
}
final Intent intent2 = new Intent("android.intent.action.MEDIA_BUTTON");
intent2.putExtra("android.intent.extra.KEY_EVENT", (Parcelable)new KeyEvent(0, 79)); // шлем действие ACTION_DOWN с кодом KEYCODE_HEADSETHOOK
context.sendBroadcast(intent2, "android.permission.CALL_PRIVILEGED");
final Intent intent3 = new Intent("android.intent.action.MEDIA_BUTTON");
intent3.putExtra("android.intent.extra.KEY_EVENT", (Parcelable)new KeyEvent(1, 79)); // шлем действие ACTION_UP с кодом KEYCODE_HEADSETHOOK
context.sendBroadcast(intent3, "android.permission.CALL_PRIVILEGED");
if (!this.a && !this.b && !j.b()) {
final Intent intent4 = new Intent("android.intent.action.HEADSET_PLUG");
intent4.addFlags(1073741824);
intent4.putExtra("state", 0);
intent4.putExtra("name", "Headset");
context.sendOrderedBroadcast(intent4, (String)null);
}
if (b && j.b()) {
return;
}
}
}
void a() {
try {
final DataOutputStream dataOutputStream = new DataOutputStream(Runtime.getRuntime().exec("su").getOutputStream());
dataOutputStream.writeBytes("input keyevent " + Integer.toString(6) + "
");
dataOutputStream.writeBytes("exit
");
dataOutputStream.flush();
dataOutputStream.close();
}
catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
}
public void a(final Context context) { //вызывается из слушателя
if (j.a()) { // API >= 21
this.a(context, 79, true);
return;
}
this.a(context, true);
}
void b() {
try {
final DataOutputStream dataOutputStream = new DataOutputStream(Runtime.getRuntime().exec("su").getOutputStream());
dataOutputStream.writeBytes("input keyevent " + Integer.toString(5) + "
"); //поднимает трубку
dataOutputStream.writeBytes("exit
");
dataOutputStream.flush();
dataOutputStream.close();
}
catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
}
...
}
Комментариев нет:
Отправить комментарий