It is given: there is an application Ultimate Call Screen HD - there are quite a few similar applications on the market.

Question: how does it work? How is it written?

Who has any ideas?

PS Focusing on non-rooted devices.

  • one
    In theory, you need to write an application that replaces the standard Dialer - stackoverflow.com/questions/5029183/android-dialer-application - anber
  • I believe that it is not. The specified application does not replace the diver, but only decorates it. - Barmaley
  • As far as I remember, even those prog. No 100% quality solution, no, no, yes, anyway, the standard screen of the dialer skips. - aratj
  • Nevertheless, I would like to understand the device of this miracle. Well, not to engage in reverse engineering :) - Barmaley
  • @Barmaley: why are you reverse-engineering - then squeamish, my friend? In the case of Java, this is actually just an awkward way to watch the source =) - Mints97

3 answers 3

TL; DR
The application is presented to the headset system in order to receive / end calls

The Ultimate Call Screen HD application in the manifest states the following:

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> <uses-permission android:name="android.permission.CALL_PHONE"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <application ...> <service android:enabled="true" android:name="com.lowveld.ucs.service.OutcallService"/> <service android:enabled="true" android:name="com.lowveld.ucs.service.InCallService"/> <receiver android:enabled="true" android:name="com.lowveld.ucs.receivers.PhoneReceiver"> <intent-filter android:priority="2147483647"> <action android:name="android.intent.action.PHONE_STATE"/> <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> <action android:name="com.lowveld.ucs.action.INDIRECT_CALL"/> </intent-filter> </receiver> ... </application> 

From the manifest, you can see that PhoneReceiver receives the first intent from the system about the outgoing / incoming call. It then delegates processing to the InCallService and OutcallService , which display the necessary interface for management and use AudioManager for voice transmission.

UPDATE: Open the InCallService class

 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 (ga("hide_status_bar", true)) { final WindowManager.LayoutParams i = this.I; i.flags |= 0x100; } else { this.I.type = 2003; } this.I.gravity = 80; if (ga("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"); } ... } 

From this code, it is clear that the application covers the standard dialer window with its CallWindowView (for this, the permission android.permission.SYSTEM_ALERT_WINDOW required). Markup for an incoming call is stored in R.layout.two_button_frame = 2130903099

 <?xml version="1.0" encoding="utf-8"?> <com.lowveld.ucs.ui.views.CallWindowView android:id="@id/callprompt_frame" ...> ... <RelativeLayout android:orientation="vertical" android:id="@id/callprompt" ...> <RelativeLayout android:orientation="vertical" android:id="@id/buttonblock" ...> ... <Button android:id="@id/answerbutton" android:text="@string/button_answer_call" ... /> <Button android:id="@id/rejectbutton" android:text="@string/button_decline_call" ... /> </RelativeLayout> ... </RelativeLayout> </com.lowveld.ucs.ui.views.CallWindowView> 

R.id.answerbutton = 2131558590
R.id.rejectbutton = 2131558495
The service has the code:

 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)); ... } 

When you click the "Accept the call" button, the listener fires

 class b implements View$OnClickListener { final InCallService a; b(final InCallService a) { super(); this.a = a; } public void onClick(final View view) { ... if (this.aa(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.ad(); } ... } } 

Which transfers control to this class:

 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 = ga("isHeadsetOn", false); this.b = ((AudioManager)context.getSystemService("audio")).isWiredHeadsetOn(); while (true) { Label_0065: { if (!ga("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 (!jb()) { 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 && !jb()) { 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 && jb()) { return; } } } void a() { try { final DataOutputStream dataOutputStream = new DataOutputStream(Runtime.getRuntime().exec("su").getOutputStream()); dataOutputStream.writeBytes("input keyevent " + Integer.toString(6) + "\n"); dataOutputStream.writeBytes("exit\n"); dataOutputStream.flush(); dataOutputStream.close(); } catch (Exception ex) { ex.printStackTrace(); throw ex; } } public void a(final Context context) { //вызывается из слушателя if (ja()) { // 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) + "\n"); //поднимает трубку dataOutputStream.writeBytes("exit\n"); dataOutputStream.flush(); dataOutputStream.close(); } catch (Exception ex) { ex.printStackTrace(); throw ex; } } ... } 
  • Well, finally - it is already warmer ... But are there any ideas how InCallService and OutCallService find the way to the dialer interface itself? The root is in this. And the program does not change the functionality of the dialer itself, but only replaces the buttons and the background. - Barmaley
  • @Barmaley ready application analysis. It turned out a lot, but brought the essence to the beginning of the answer - DeKaNszn
  • That's great! Done - Barmaley

I do not know how this is done in this application to which you refer. However, if I wanted to do something like this, I would do this:

  1. Intercept incoming and outgoing call events, as well as other events if they are needed to handle a call — there are quite a few links to do this, including in the answers to this question.

Here's another one - https://stackoverflow.com/questions/5990590/how-to-detect-phone-call-broadcast-receiver-in-android

  1. Create a beautiful custom view to show what is happening. Make settings that allow you to select a photo by phone number, and set it in the background, place the buttons on the top / bottom, etc. Here you can show the imagination and skills of the interface designer - do what you want, add social. networks, photos, avatars, ratings - what you want, and add that.

  2. Handle user actions, similar to the default application.

In one of the comments you write this:

And the program does not change the functionality of the dialer itself, but only replaces the buttons and the background.

I would say that the program does not replace the buttons and background, but completely repeats the functionality of the "dialer". They just recreated everything that was, added styles and other settings, so that the end user can change what he wants when he wants.

  1. I read a few topics on the issue. Indeed, a lot of things from the first time did not understand correctly. However, nothing prevents you from displaying our beautiful View over a standard dialer?
  • 3
    you do not even understand the whole depth of your ignorance (I almost said ignorance) and only this stops me from minus your answer: 1) You cannot substitute your beautifully collected View into the place of a dialer 2) You cannot replace the system dialer (without root) 3 ) The hack you are describing about android:priority is known for 100 years, although it doesn’t work in new versions 4) Tales about what's with If on some device the maximum priority is already set - your application on such a device will simply not be called - leave it on your conscience. - Barmaley
  • stackoverflow.com/questions/9869314/… - apparently not quite in the place of the dialer, but rather on top of it. - Toly
  • In the description of the application there is this: "If you can go back to the normal dialer with one button, perhaps this button simply removes top layer - Toly
  • Edited the answer. In the last link in the comments - the accepted answer describes how to make your View over the default dialer. Apparently it just starts up with some delay. - Toly
  • I read more on the topic - in general, when you call, Broadcast is sent, which is intercepted by all applications with the necessary rights. You cannot cancel a request because it is not ordered. There are a bunch of code examples of how people add and display any information on top of a standard dialer. If you go further - there are code examples that are completely superimposed over this dialer - it is not even visible. - Toly

So there, in my opinion, a separate special microcontroller processes the audio stream from the call and this is not the ARM processor on which the device itself actually works. To him, even through the NDK can not obviously get through.

And through Jav, it seems that you can only change the standard interface and use Intent.ACTION_CALL as it is written there, and as it seems, it makes Ultimate Call Screen HD too.

Here are examples of threads where this was discussed:

https://groups.google.com/forum/#!topic/android-ndk/HNA-j0KnfII

https://groups.google.com/forum/#!msg/android-developers/AbU85mtDgQw/q_V_ACV1724J

True, they are from 2009, but I do not think that the situation with iron has suddenly changed.

Here is a description of the bus on which the audio transmission is carried out:

  • Yes, I do not need an audio path - I just need to replace the background on the caller. - Barmaley
  • But I understood, well, there apparently is just a full-screen activation that has all the privileges for calls and to which the entire necessary interface is attached. But more specifically on the implementation can not say anything. - igumnov