¿Cómo detectar llamadas entrantes en un dispositivo Android?

130

Estoy tratando de hacer una aplicación como, cuando llega una llamada al teléfono, quiero detectar el número. A continuación se muestra lo que intenté, pero no detecta llamadas entrantes.

Quiero ejecutar mi MainActivityfondo, ¿cómo puedo hacer eso?

Había dado el permiso en el manifestarchivo.

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

¿Hay algo más que deba proporcionar en el manifiesto?

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_layout);
   }

   public class myPhoneStateChangeListener extends PhoneStateListener {
       @Override
       public void onCallStateChanged(int state, String incomingNumber) {
           super.onCallStateChanged(state, incomingNumber);
           if (state == TelephonyManager.CALL_STATE_RINGING) {
               String phoneNumber =   incomingNumber;
           }
       }
   }
}
Jesbin MJ
fuente
¿Qué deberíamos hacer para Android P?
Ahmad Arslan

Respuestas:

336

Esto es lo que uso para hacer esto:

Manifiesto:

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

<!--This part is inside the application-->
    <receiver android:name=".CallReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.PHONE_STATE" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
        </intent-filter>
    </receiver>

Mi base de detector de llamadas reutilizables

package com.gabesechan.android.reusable.receivers;

import java.util.Date;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;

public abstract class PhonecallReceiver extends BroadcastReceiver {

    //The receiver will be recreated whenever android feels like it.  We need a static variable to remember data between instantiations

    private static int lastState = TelephonyManager.CALL_STATE_IDLE;
    private static Date callStartTime;
    private static boolean isIncoming;
    private static String savedNumber;  //because the passed incoming is only valid in ringing


    @Override
    public void onReceive(Context context, Intent intent) {

        //We listen to two intents.  The new outgoing call only tells us of an outgoing call.  We use it to get the number.
        if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
            savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
        }
        else{
            String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
            String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
            int state = 0;
            if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)){
                state = TelephonyManager.CALL_STATE_IDLE;
            }
            else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
                state = TelephonyManager.CALL_STATE_OFFHOOK;
            }
            else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)){
                state = TelephonyManager.CALL_STATE_RINGING;
            }


            onCallStateChanged(context, state, number);
        }
    }

    //Derived classes should override these to respond to specific events of interest
    protected abstract void onIncomingCallReceived(Context ctx, String number, Date start);
    protected abstract void onIncomingCallAnswered(Context ctx, String number, Date start);
    protected abstract void onIncomingCallEnded(Context ctx, String number, Date start, Date end);

    protected abstract void onOutgoingCallStarted(Context ctx, String number, Date start);      
    protected abstract void onOutgoingCallEnded(Context ctx, String number, Date start, Date end);

    protected abstract void onMissedCall(Context ctx, String number, Date start);

    //Deals with actual events

    //Incoming call-  goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
    //Outgoing call-  goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
    public void onCallStateChanged(Context context, int state, String number) {
        if(lastState == state){
            //No change, debounce extras
            return;
        }
        switch (state) {
            case TelephonyManager.CALL_STATE_RINGING:
                isIncoming = true;
                callStartTime = new Date();
                savedNumber = number;
                onIncomingCallReceived(context, number, callStartTime);
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                //Transition of ringing->offhook are pickups of incoming calls.  Nothing done on them
                if(lastState != TelephonyManager.CALL_STATE_RINGING){
                    isIncoming = false;
                    callStartTime = new Date();
                    onOutgoingCallStarted(context, savedNumber, callStartTime);                     
                }
                else
                {
                    isIncoming = true;
                    callStartTime = new Date();
                    onIncomingCallAnswered(context, savedNumber, callStartTime); 
                }

                break;
            case TelephonyManager.CALL_STATE_IDLE:
                //Went to idle-  this is the end of a call.  What type depends on previous state(s)
                if(lastState == TelephonyManager.CALL_STATE_RINGING){
                    //Ring but no pickup-  a miss
                    onMissedCall(context, savedNumber, callStartTime);
                }
                else if(isIncoming){
                    onIncomingCallEnded(context, savedNumber, callStartTime, new Date());                       
                }
                else{
                    onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());                                               
                }
                break;
        }
        lastState = state;
    }
}

Luego, para usarlo, simplemente deriva una clase de él e implementa algunas funciones fáciles, cualesquiera que sean los tipos de llamadas que te interesen:

public class CallReceiver extends PhonecallReceiver {

    @Override
    protected void onIncomingCallReceived(Context ctx, String number, Date start)
    {
        //
    }

    @Override
    protected void onIncomingCallAnswered(Context ctx, String number, Date start)
    {
        //
    }

    @Override
    protected void onIncomingCallEnded(Context ctx, String number, Date start, Date end)
    {
        //
    }

    @Override
    protected void onOutgoingCallStarted(Context ctx, String number, Date start)
    {
        //
    } 

    @Override 
    protected void onOutgoingCallEnded(Context ctx, String number, Date start, Date end)
    {
        //
    }

    @Override
    protected void onMissedCall(Context ctx, String number, Date start)
    {
        //
    }

}

Además, puedes ver una reseña que hice sobre por qué el código es como está en mi blog . Enlace general: https://gist.github.com/ftvs/e61ccb039f511eb288ee

EDITAR: actualizado a un código más simple, ya que he modificado la clase para mi uso

Gabe Sechan
fuente
2
Este código no muestra nada. Lo que hace es llamarlo cuando comienza / finaliza una llamada saliente, y le pasa el número, la hora de inicio y la hora de finalización. En realidad, mostrarlo es tu trabajo, porque no tengo forma de saber cómo quieres que se haga eso.
Gabe Sechan
3
@ GabeSechan: ¡Impresionante! ¿Me puede guiar para manejar la situación de llamada en espera?
Mehul Joisar
66
Solo para agregar a esto, no funcionó cuando la aplicación no estaba en primer plano o en segundo plano hasta que agregué esto en el receptor: "android: enabled =" true "
Rajat Sharma
1
Las variables estáticas se mantendrán hasta que la aplicación sea expulsada de la memoria (que puede ser bastante tiempo, dependiendo de si los servicios se están ejecutando y las condiciones generales de la memoria del teléfono). Pero sí, se pueden perder. Puede escribirlo en el disco, digamos a través de preferencias compartidas, pero eso también puede provocar que tenga resultados falsos, ya que evitaría que sus datos se borren correctamente en varios casos, como al reiniciar el teléfono. Para mi caso de uso, los datos raros y perdidos eran mejores que los datos incorrectos. Siéntase libre de jugar con esto a sus necesidades.
Gabe Sechan
1
@ GabeSechan: Parece que hay un error en él. lastState no debe inicializarse a CALL_STATE_IDLE. Me faltan algunas llamadas cuando mi aplicación se mata mientras el estado actual es RINGING. Porque cuando se vuelve IDLEa hacer cuando finaliza la llamada, la variable estática se reinicia CALL_STATE_IDLEy no se desprende haciendo nada. Entonces perdemos la referencia a lastState.
Heisenberg
23
private MyPhoneStateListener phoneStateListener = new MyPhoneStateListener();

para registrarse

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

y anular el registro

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
stinepike
fuente
¿Dónde debería usar esto en mainactividad?
Jesbin MJ
coloque esto en la clase donde está escuchando el cambio ... normalmente los registros se realizan en oncreate y se anula el registro en ondestroy ... declare el objeto globalmente en la clase
stinepike
Esta opción no requiere permisos.
Mike
Si bien la solución de Gabe se adapta mejor a las funciones más intrusivas, es decir, el tipo de aplicación Viber, esta es la mejor solución para aquellos que necesitan simplemente reaccionar ante las acciones del usuario de una llamada telefónica. Solicitar permiso de tiempo de ejecución en casos como este es muy probable que sea una exageración y podría no ser bien recibido por el usuario.
bosphere
1
Quiero hacer algo, cuando llegue la llamada, ¿cómo hacer con este código?
Noor Hossain
14

Con Android P - Api Nivel 28: necesita obtener el permiso READ_CALL_LOG

Acceso restringido a registros de llamadas

P Android mueve los CALL_LOG, READ_CALL_LOG, WRITE_CALL_LOG, y PROCESS_OUTGOING_CALLSlos permisos del PHONEgrupo de permisos para el nuevo CALL_LOGgrupo de permisos. Este grupo ofrece a los usuarios un mejor control y visibilidad de las aplicaciones que necesitan acceso a información confidencial sobre llamadas telefónicas, como leer registros de llamadas telefónicas e identificar números telefónicos.

Para leer los números de la acción intencional PHONE_STATE, necesita tanto el READ_CALL_LOGpermiso como el READ_PHONE_STATEpermiso . Para leer números onCallStateChanged(), ahora solo necesita el READ_CALL_LOGpermiso. Ya no necesitas el READ_PHONE_STATEpermiso.

atasoyh
fuente
para aquellos que sólo están agregando READ_CALL_LOGen el AndroidManifest.xmlenfoque en la adición de una solicitud de permiso de MainActivity.
Piyush
13

ACTUALIZACIÓN: El código realmente impresionante publicado por Gabe Sechan ya no funciona a menos que solicite explícitamente al usuario que otorgue los permisos necesarios. Aquí hay un código que puede colocar en su actividad principal para solicitar estos permisos:

    if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
            != PackageManager.PERMISSION_GRANTED) {
        // Permission has not been granted, therefore prompt the user to grant permission
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.READ_PHONE_STATE},
                MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
    }

    if (getApplicationContext().checkSelfPermission(Manifest.permission.PROCESS_OUTGOING_CALLS)
            != PackageManager.PERMISSION_GRANTED) {
        // Permission has not been granted, therefore prompt the user to grant permission
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.PROCESS_OUTGOING_CALLS},
                MY_PERMISSIONS_REQUEST_PROCESS_OUTGOING_CALLS);
    }

TAMBIÉN: Como alguien mencionó en un comentario debajo de la publicación de Gabe , debe agregar un pequeño fragmento de código android:enabled="trueal receptor para detectar llamadas entrantes cuando la aplicación no se está ejecutando actualmente en primer plano:

    <!--This part is inside the application-->
    <receiver android:name=".CallReceiver" android:enabled="true">
        <intent-filter>
            <action android:name="android.intent.action.PHONE_STATE" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
        </intent-filter>
    </receiver>
topherPedersen
fuente
¿Qué sucede si la aplicación no tiene ninguna actividad y solo el receptor de transmisión y un servicio? Entonces, ¿dónde escribimos este código para obtener el permiso del usuario ya que no se llamará al receptor de la transmisión hasta que se otorgue este permiso?
user2779311
2
Al menos necesita una actividad principal incluso si solo se abre una vez. Tome mi aplicación de bloqueo de llamadas RoboStop, por ejemplo: cuando el usuario descarga la aplicación por primera vez y luego hace clic en el ícono de la aplicación para iniciarla, se le solicita que le otorgue los permisos necesarios. La aplicación también cuenta con un botón para habilitar / deshabilitar el bloqueo de llamadas, pero el usuario no necesita iniciar la aplicación / actividad nuevamente, el bloqueo de llamadas se realizará en segundo plano sin que el usuario tenga que iniciar la aplicación / actividad nuevamente.
topherPedersen
Aquellos que tengan problemas para implementar esto, sigan este tutorial studytutorial.in/…
Prasath
5

esto puede ayudarlo y también agregar requerir permiso

public class PhoneListener extends PhoneStateListener
{
    private Context context;
    public static String getincomno;

    public PhoneListener(Context c) {
        Log.i("CallRecorder", "PhoneListener constructor");
        context = c;
    }

    public void onCallStateChanged (int state, String incomingNumber)
    {

        if(!TextUtils.isEmpty(incomingNumber)){
        // here for Outgoing number make null to get incoming number
        CallBroadcastReceiver.numberToCall = null;
        getincomno = incomingNumber;
        }

        switch (state) {
        case TelephonyManager.CALL_STATE_IDLE:

            break;
        case TelephonyManager.CALL_STATE_RINGING:
            Log.d("CallRecorder", "CALL_STATE_RINGING");
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK:

            break;
        }
    }
}
Ankitkumar Makwana
fuente
2
Esto parece estar bien. Pero, ¿cómo puedo usarlo desde la actividad? por favor dime detalles
Amir
2

Aquí hay un método simple que puede evitar el uso PhonestateListenery otras complicaciones.
Así que aquí estamos recibiendo los 3 eventos de Android como RINGING, OFFHOOKy IDLE. Y con el fin de obtener el posible estado de llamada, tenemos que definir nuestros propios estados como RINGING, OFFHOOK, IDLE, FIRST_CALL_RINGING, SECOND_CALL_RINGING. Puede manejar todos los estados en una llamada telefónica.
Piense de alguna manera que estamos recibiendo eventos de Android y definiremos nuestros estados de guardia. Mira el código.

public class CallListening  extends BroadcastReceiver {
    private static final String TAG ="broadcast_intent";
    public static String incoming_number;
    private String current_state,previus_state,event;
    public static Boolean dialog= false;
    private Context context;
    private SharedPreferences sp,sp1;
    private SharedPreferences.Editor spEditor,spEditor1;
    public void onReceive(Context context, Intent intent) {
        //Log.d("intent_log", "Intent" + intent);
        dialog=true;
        this.context = context;
        event = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
        incoming_number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
        Log.d(TAG, "The received event : "+event+", incoming_number : " + incoming_number);
        previus_state = getCallState(context);
        current_state = "IDLE";
        if(incoming_number!=null){
            updateIncomingNumber(incoming_number,context);
        }else {
            incoming_number=getIncomingNumber(context);
        }
        switch (event) {
            case "RINGING":
                Log.d(TAG, "State : Ringing, incoming_number : " + incoming_number);
            if((previus_state.equals("IDLE")) || (previus_state.equals("FIRST_CALL_RINGING"))){
                    current_state ="FIRST_CALL_RINGING";
                }
                if((previus_state.equals("OFFHOOK"))||(previus_state.equals("SECOND_CALL_RINGING"))){
                    current_state = "SECOND_CALL_RINGING";
                }

                break;
            case "OFFHOOK":
                Log.d(TAG, "State : offhook, incoming_number : " + incoming_number);
                if((previus_state.equals("IDLE")) ||(previus_state.equals("FIRST_CALL_RINGING")) || previus_state.equals("OFFHOOK")){
                    current_state = "OFFHOOK";
                }
                if(previus_state.equals("SECOND_CALL_RINGING")){
                    current_state ="OFFHOOK";
                    startDialog(context);
                }
                break;
            case "IDLE":
                Log.d(TAG, "State : idle and  incoming_number : " + incoming_number);
                if((previus_state.equals("OFFHOOK")) || (previus_state.equals("SECOND_CALL_RINGING")) || (previus_state.equals("IDLE"))){
                    current_state="IDLE";
                }
                if(previus_state.equals("FIRST_CALL_RINGING")){
                    current_state = "IDLE";
                    startDialog(context);
                }
                updateIncomingNumber("no_number",context);
                Log.d(TAG,"stored incoming number flushed");
                break;
        }
        if(!current_state.equals(previus_state)){
            Log.d(TAG, "Updating  state from "+previus_state +" to "+current_state);
            updateCallState(current_state,context);

        }
    }
    public void startDialog(Context context) {
        Log.d(TAG,"Starting Dialog box");
        Intent intent1 = new Intent(context, NotifyHangup.class);
        intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent1);

    }
    public void updateCallState(String state,Context context){
        sp = PreferenceManager.getDefaultSharedPreferences(context);
        spEditor = sp.edit();
        spEditor.putString("call_state", state);
        spEditor.commit();
        Log.d(TAG, "state updated");

    }
    public void updateIncomingNumber(String inc_num,Context context){
        sp = PreferenceManager.getDefaultSharedPreferences(context);
        spEditor = sp.edit();
        spEditor.putString("inc_num", inc_num);
        spEditor.commit();
        Log.d(TAG, "incoming number updated");
    }
    public String getCallState(Context context){
        sp1 = PreferenceManager.getDefaultSharedPreferences(context);
        String st =sp1.getString("call_state", "IDLE");
        Log.d(TAG,"get previous state as :"+st);
        return st;
    }
    public String getIncomingNumber(Context context){
        sp1 = PreferenceManager.getDefaultSharedPreferences(context);
        String st =sp1.getString("inc_num", "no_num");
        Log.d(TAG,"get incoming number as :"+st);
        return st;
    }
}
ARUNBALAN NV
fuente
0

@ Gabe Sechan, gracias por tu código. Funciona bien excepto el onOutgoingCallEnded(). Nunca se ejecuta. Los teléfonos de prueba son Samsung S5 y Trendy. Hay 2 errores, creo.

1: faltan un par de paréntesis.

case TelephonyManager.CALL_STATE_IDLE: 
    // Went to idle-  this is the end of a call.  What type depends on previous state(s)
    if (lastState == TelephonyManager.CALL_STATE_RINGING) {
        // Ring but no pickup-  a miss
        onMissedCall(context, savedNumber, callStartTime);
    } else {
        // this one is missing
        if(isIncoming){
            onIncomingCallEnded(context, savedNumber, callStartTime, new Date());                       
        } else {
            onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());                                               
        }
    }
    // this one is missing
    break;

2: lastStateno se actualiza statesi está al final de la función. Debe ser reemplazado a la primera línea de esta función por

public void onCallStateChanged(Context context, int state, String number) {
    int lastStateTemp = lastState;
    lastState = state;
    // todo replace all the "lastState" by lastStateTemp from here.
    if (lastStateTemp  == state) {
        //No change, debounce extras
        return;
    }
    //....
}

Adicional he puesto lastStatey savedNumberen preferencia compartida como usted sugirió.

Solo lo probé con los cambios anteriores. Error solucionado al menos en mis teléfonos.

Diiiiii
fuente
0

Por favor use el siguiente código. Le ayudará a obtener el número entrante con otros detalles de la llamada.

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<TextView
    android:id="@+id/call"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:text="@string/hello_world" />

</RelativeLayout>

MainActivity.java

public class MainActivity extends Activity {

private static final int MISSED_CALL_TYPE = 0;
private TextView txtcall;

@Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    txtcall = (TextView) findViewById(R.id.call);

    StringBuffer sb = new StringBuffer();
    Cursor managedCursor = managedQuery(CallLog.Calls.CONTENT_URI, null,
            null, null, null);
    int number = managedCursor.getColumnIndex(CallLog.Calls.NUMBER);
    int type = managedCursor.getColumnIndex(CallLog.Calls.TYPE);
    int date = managedCursor.getColumnIndex(CallLog.Calls.DATE);
    int duration = managedCursor.getColumnIndex(CallLog.Calls.DURATION);
    sb.append("Call Details :");
    while (managedCursor.moveToNext()) {
        String phNumber = managedCursor.getString(number);
        String callType = managedCursor.getString(type);
        String callDate = managedCursor.getString(date);
        Date callDayTime = new Date(Long.valueOf(callDate));
        String callDuration = managedCursor.getString(duration);
        String dir = null;
        int dircode = Integer.parseInt(callType);
        switch (dircode) {

        case CallLog.Calls.OUTGOING_TYPE:
            dir = "OUTGOING";
            break;

        case CallLog.Calls.INCOMING_TYPE:
            dir = "INCOMING";
            break;

        case CallLog.Calls.MISSED_TYPE:
            dir = "MISSED";
            break;
        }
        sb.append("\nPhone Number:--- " + phNumber + " \nCall Type:--- "
                + dir + " \nCall Date:--- " + callDayTime
                + " \nCall duration in sec :--- " + callDuration);
        sb.append("\n----------------------------------");
    }
    managedCursor.close();
    txtcall.setText(sb);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
}

} 

y en su solicitud de manifiesto para los siguientes permisos:

<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
usuario2173696
fuente
READ_LOGS lo hace para que su aplicación sea prohibida en Play Store
Duna
0

Necesita un BroadcastReceiver para que ACTION_PHONE_STATE_CHANGEDesto lo reciba cuando el estado del teléfono cambie de inactivo, sonando, descolgado del valor anterior y del nuevo valor que puede detectar si se trata de una llamada entrante / saliente.

El permiso requerido sería:

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

Pero si también desea recibir EXTRA_INCOMING_NUMBER en esa transmisión, necesitará otro permiso: "android.permission.READ_CALL_LOG"

Y el código algo como esto:

val receiver: BroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d(TAG, "onReceive")
    }
}

override fun onResume() {
    val filter = IntentFilter()
    filter.addAction("android.intent.action.PHONE_STATE")
    registerReceiver(receiver, filter)
    super.onResume()
}

override fun onPause() {
    unregisterReceiver(receiver)
    super.onPause()
}

y en la clase de receptor, podemos obtener el estado actual leyendo una intención como esta:

intent.extras["state"]

El resultado de los extras podría ser:

TIMBRE -> Si su teléfono está sonando

DESCONEXIÓN -> Si está hablando con alguien (llamada entrante o saliente)

IDLE -> si la llamada finalizó (llamada entrante o saliente)

Con la transmisión PHONE_STATE no necesitamos usar el permiso PROCESS_OUTGOING_CALLS o la acción obsoleta NEW_OUTGOING_CALL.

FarshidABZ
fuente