¿Cómo puedo saber si la aplicación de Android se está ejecutando en primer plano?

83

Estoy haciendo una notificación de la barra de estado en mi aplicación de Android que es activada por c2dm. No quiero mostrar la notificación si la aplicación se está ejecutando. ¿Cómo se determina si la aplicación se está ejecutando y está en primer plano?

Andrew Thomas
fuente
Es una pregunta similar ... aunque probé una bandera en onStart / onStop y no funcionó. Todavía no entiendo la diferencia entre detener / iniciar y pausar / reanudar.
Andrew Thomas
1
Debe utilizar esta solución: stackoverflow.com/questions/3667022/…
Informatic0re
Desde API 16, hay una forma más sencilla de usar ActivityManager.getMyMemoryState
Juozas Kontvainis
Dado que admite la versión 26 de la biblioteca, simplemente tiene que consultar ProcessLifecycleOwner cuando lo desee. Compruebe stackoverflow.com/a/52678290/6600000
Keivan Esbati

Respuestas:

55

Haga una variable global como private boolean mIsInForegroundMode;y asigne un falsevalor en onPause()y un truevalor en onResume().

Código de muestra:

private boolean mIsInForegroundMode;

@Override
protected void onPause() {
    super.onPause();
    mIsInForegroundMode = false;
}

@Override
protected void onResume() {
    super.onResume();
    mIsInForegroundMode = true;
}

// Some function.
public boolean isInForeground() {
    return mIsInForegroundMode;
}
Wroclai
fuente
2
@MurVotema: Sí, lo es. Pero somos libres de pasar esta variable, por ejemplo, a preferencias o una base de datos cada vez que se produce un cambio.
Wroclai
19
@Shelly Pero su variable se volvería Verdadero / Falso / Verdadero mucho al cambiar de actividades dentro de la misma aplicación. Esto significa que debe tener una histéresis para detectar realmente cuándo su aplicación pierde el primer plano.
Radu
10
Ésta no es la mejor solución. Verifique la solución @Gadenkan a continuación.
Felipe Lima
Funciona para escenarios simples, pero si tiene un onActivityResult () en su actividad, las cosas se complican. Quiero ejecutar un servicio en segundo plano si y solo si todas las actividades de mi aplicación están en segundo plano, y esta solución anterior se queda corta.
Josh
1
@Tima Si desea que esté disponible para todas las actividades, puede crear una nueva MyOwnActivity extiende AppCompatActivity ... Luego, dentro de MyOwnActivity, anule tanto onResume () / onPause (), y por último extiende todas las actividades de su aplicación desde MyOwnActivity en lugar de AppCompatActivity. Problema resuelto. ; )
elliotching
112

Alternativamente, puede verificar ActivityManagerqué tareas se están ejecutando por getRunningTasksmétodo. Luego verifique con la primera tarea (tarea en primer plano) en la Lista de tareas devuelta, si es su tarea.
Aquí está el ejemplo de código:

public Notification buildNotification(String arg0, Map<String, String> arg1) {

    ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningTaskInfo> services = activityManager
            .getRunningTasks(Integer.MAX_VALUE);
    boolean isActivityFound = false;

    if (services.get(0).topActivity.getPackageName().toString()
            .equalsIgnoreCase(appContext.getPackageName().toString())) {
        isActivityFound = true;
    }

    if (isActivityFound) {
        return null;
    } else {
        // write your code to build a notification.
        // return the notification you built here
    }

}

Y no olvide agregar el GET_TASKSpermiso en el archivo manifest.xml para poder ejecutar el getRunningTasks()método en el código anterior:

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

p / s: Si está de acuerdo de esta manera, tenga en cuenta que este permiso ahora está obsoleto.

Gadenkan
fuente
24
Si desea utilizar este código, no olvide agregar <uses-allow android: name = "android.permission.GET_TASKS" />
Lakshmanan
13
Nitpicks: invocar toString()una cadena devuelta por getPackageName()es redundante. Además, como solo nos interesa la primera tarea devuelta por getRunningTasks(), podemos pasar en 1lugar de Integer.MAX_VALUE.
Jonik
14
Nota: este método solo está destinado a depurar y presentar interfaces de usuario de gestión de tareas. Esto nunca debe usarse para la lógica central en una aplicación, como decidir entre diferentes comportamientos según la información que se encuentra aquí. Dichos usos no son compatibles y probablemente se interrumpirán en el futuro. Por ejemplo, si varias aplicaciones se pueden ejecutar activamente al mismo tiempo, las suposiciones hechas sobre el significado de los datos aquí para propósitos de flujo de control serán incorrectas.
Informatic0re
5
Desafortunadamente, getRunningTasks () ha quedado obsoleto desde Android L (API 20). A partir de L, este método ya no está disponible para aplicaciones de terceros: la introducción de recientes centrados en documentos significa que puede filtrar información personal a la persona que llama. Para compatibilidad con versiones anteriores, seguirá devolviendo un pequeño subconjunto de sus datos: al menos las propias tareas de la persona que llama y posiblemente algunas otras tareas, como el hogar, que se sabe que no son confidenciales.
Sam Lu
2
Es de suponer que si solo muestra las tareas de la persona que llama, entonces la primera tarea y la actividad principal siempre serán suyas, lo que significa que esto siempre será verdadero después de la piruleta.
Mike Holler
46

Esta es una publicación bastante antigua pero aún bastante relevante. La solución aceptada anteriormente puede funcionar, pero es incorrecta. Como escribió Dianne Hackborn:

Estas API no están ahí para que las aplicaciones basen su flujo de interfaz de usuario, sino para hacer cosas como mostrarle al usuario las aplicaciones en ejecución, un administrador de tareas, etc.

Sí, hay una lista guardada en la memoria para estas cosas. Sin embargo, está apagado en otro proceso, administrado por hilos que se ejecutan por separado de los suyos, y no es algo con lo que pueda contar (a) ver a tiempo para tomar la decisión correcta o (b) tener una imagen consistente para cuando regrese. Además, la decisión sobre cuál es la "próxima" actividad a la que ir siempre se realiza en el punto en el que se producirá el cambio, y no es hasta ese punto exacto (donde el estado de actividad se bloquea brevemente para realizar el cambio) cuando realmente sé por tal cuál será la próxima cosa.

Y no se garantiza que la implementación y el comportamiento global aquí sigan siendo los mismos en el futuro.

La solución correcta es implementar: ActivityLifeCycleCallbacks .

Básicamente, esto necesita una clase de aplicación y el controlador se puede configurar allí para identificar el estado de sus actividades en la aplicación.

Vinay
fuente
7
Aquí hay un ejemplo de cómo usarlo baroqueworksdev.blogspot.com/2012/12/…
JK
2
Creo que esta es la mejor solución y probablemente no se romperá en el futuro. Me pregunto por qué no obtuvo suficientes votos.
Rohan Kandwal
2
Si escribe un código de ejemplo, obtendrá más votos porque algunas personas se saltan las respuestas sin ejemplo ... pero es la mejor solución ...
Saman Salehi
¿En qué se diferencia esto del método predominante onPausey onResumeutilizado en la respuesta aceptada?
Saitama
24

Como dice Vinay, probablemente la mejor solución (para admitir versiones más nuevas de Android, 14+) es usarla ActivityLifecycleCallbacksen la Applicationimplementación de la clase.

package com.telcel.contenedor.appdelegate;

import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;

/** Determines global app lifecycle states. 
 * 
 * The following is the reference of activities states:
 * 
 * The <b>visible</b> lifetime of an activity happens between a call to onStart()
 * until a corresponding call to onStop(). During this time the user can see the
 * activity on-screen, though it may not be in the foreground and interacting with 
 * the user. The onStart() and onStop() methods can be called multiple times, as 
 * the activity becomes visible and hidden to the user.
 * 
 * The <b>foreground</b> lifetime of an activity happens between a call to onResume()
 * until a corresponding call to onPause(). During this time the activity is in front
 * of all other activities and interacting with the user. An activity can frequently
 * go between the resumed and paused states -- for example when the device goes to
 * sleep, when an activity result is delivered, when a new intent is delivered -- 
 * so the code in these methods should be fairly lightweight. 
 * 
 * */
public class ApplicationLifecycleManager implements ActivityLifecycleCallbacks {

    /** Manages the state of opened vs closed activities, should be 0 or 1. 
     * It will be 2 if this value is checked between activity B onStart() and
     * activity A onStop().
     * It could be greater if the top activities are not fullscreen or have
     * transparent backgrounds.
     */
    private static int visibleActivityCount = 0;

    /** Manages the state of opened vs closed activities, should be 0 or 1
     * because only one can be in foreground at a time. It will be 2 if this 
     * value is checked between activity B onResume() and activity A onPause().
     */
    private static int foregroundActivityCount = 0;

    /** Returns true if app has foreground */
    public static boolean isAppInForeground(){
        return foregroundActivityCount > 0;
    }

    /** Returns true if any activity of app is visible (or device is sleep when
     * an activity was visible) */
    public static boolean isAppVisible(){
        return visibleActivityCount > 0;
    }

    public void onActivityCreated(Activity activity, Bundle bundle) {
    }

    public void onActivityDestroyed(Activity activity) {
    }

    public void onActivityResumed(Activity activity) {
        foregroundActivityCount ++;
    }

    public void onActivityPaused(Activity activity) {
        foregroundActivityCount --;
    }


    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    public void onActivityStarted(Activity activity) {
        visibleActivityCount ++;
    }

    public void onActivityStopped(Activity activity) {
        visibleActivityCount --;
    }
}

Y en el onCreate()método de aplicación :

registerActivityLifecycleCallbacks(new ApplicationLifecycleManager());

Entonces ApplicationLifecycleManager.isAppVisible()o ApplicationLifecycleManager.isAppInForeground()se usaría para conocer el estado deseado.

htafoya
fuente
Solo funcionará con isAppVisible (). Si cierra la aplicación, envíela al primer plano, no tendrá efecto
AlwaysConfused
19

Desde API 16 puedes hacerlo así:

static boolean shouldShowNotification(Context context) {
    RunningAppProcessInfo myProcess = new RunningAppProcessInfo();
    ActivityManager.getMyMemoryState(myProcess);
    if (myProcess.importance != RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
        return true;

    KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
    // app is in foreground, but if screen is locked show notification anyway
    return km.inKeyguardRestrictedInputMode();
}
Juozas Kontvainis
fuente
Solución eficiente. Gracias :)
Md. Sajedul Karim
Muy buena solucion. No se requieren permisos.
user3144836
¡Increíble! Estaba usando la lista de tareas, pero requiere OBTENER TAREA ... ¡realmente aprecio esto!
hexágono
15

Para su información, si usa la solución Gadenkan (¡que es genial!) No olvide agregar

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

al manifiesto.

bertz94
fuente
9
En Android Lollipop, este permiso está en desuso
Mussa
14

Versión ligeramente limpia de la solución de Gadenkan . Ponlo en cualquier actividad, o tal vez una clase base para todas tus actividades.

protected boolean isRunningInForeground() {
    ActivityManager manager = 
         (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> tasks = manager.getRunningTasks(1);
    if (tasks.isEmpty()) {
        return false;
    }
    String topActivityName = tasks.get(0).topActivity.getPackageName();
    return topActivityName.equalsIgnoreCase(getPackageName());
}

Para poder llamar getRunningTasks(), debe agregar esto en su AndroidManifest.xml:

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

Sin ActivityManager.getRunningTasks() embargo, tenga en cuenta lo que dice Javadoc:

Nota: este método solo está destinado a depurar y presentar interfaces de usuario de gestión de tareas. Esto nunca debe usarse para la lógica central en una aplicación, como decidir entre diferentes comportamientos según la información que se encuentra aquí. Dichos usos no son compatibles y probablemente se interrumpirán en el futuro.

Actualización (febrero de 2015)

¡Tenga en cuenta que getRunningTasks()quedó obsoleto en el nivel de API 21 !

A partir de LOLLIPOP, este método ya no está disponible para aplicaciones de terceros: la introducción de recientes centrados en documentos significa que puede filtrar información de la persona a la persona que llama. Para compatibilidad con versiones anteriores, todavía devolverá un pequeño subconjunto de sus datos: al menos las propias tareas de la persona que llama y posiblemente algunas otras tareas, como el hogar, que se sabe que no son confidenciales.

Entonces, lo que escribí antes es aún más relevante:

En muchos casos, probablemente pueda encontrar una solución mejor. Por ejemplo, hacer algo en onPause()y onResume(), quizás, en una BaseActivity para todas sus actividades.

(En nuestro caso, no queríamos que se lanzara una actividad de alerta fuera de línea si no estamos en primer plano, por lo que en BaseActivity onPause()simplemente cancelamos la suscripción a la señal de RxJava que Subscriptionescucha "se desconectó").

Jonik
fuente
¿Puede publicar el código RxJava que ha utilizado? Quiero usar RxJava y no quiero pasar mucho tiempo aprendiendo RxJava en este momento :(
MBH
@MBH: Usar RxJava probablemente no será muy fructífero sin dedicar un tiempo a familiarizarse con los conceptos básicos ... :-) En cualquier caso, aquí hay un pequeño ejemplo .
Jonik
9

Siguiendo la respuesta de Gadenkan, necesitaba algo como esto para poder saber si mi aplicación no se estaba ejecutando en primer plano, pero necesitaba algo que fuera para toda la aplicación y no requiriera que configurara / desarmara banderas en toda mi aplicación.

El código de Gadenkan casi dio en el clavo, pero no estaba en mi propio estilo y sentí que podría ser más ordenado, por lo que en mi aplicación está condensado en esto.

if (!context.getPackageName().equalsIgnoreCase(((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1).get(0).topActivity.getPackageName()))
{
// App is not in the foreground
}

(Nota al margen: puede quitar el! Si desea que el cheque funcione al revés)

Aunque con este enfoque necesitas el GET_TASKSpermiso.

CrosseyeJack
fuente
3

A partir de la versión 26 de la biblioteca de soporte, puede usar ProcessLifecycleOwner para determinar el estado actual de la aplicación, simplemente agréguela a sus dependencias como se describe aquí , por ejemplo:

dependencies {
    def lifecycle_version = "1.1.1"

    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData).
    //     Support library depends on this lightweight import
    implementation "android.arch.lifecycle:runtime:$lifecycle_version"
    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // use kapt for Kotlin
}

Ahora puede consultar ProcessLifecycleOwnercada vez que desee verificar el estado de la aplicación, por ejemplo, para verificar si la aplicación se está ejecutando en primer plano, solo tiene que hacer esto:

 boolean isAppInForeground = ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
 if(!isAppInForeground)
    //Show Notification in status bar
Keivan Esbati
fuente
1
Esta es la respuesta correcta. Muchos de los otros deberían considerarse obsoletos
11m0
Para AndroidX, use Java implementation 'androidx.lifecycle:lifecycle-process:2.2.0'en su proyecto gradle.
Jonathan
1

Según las diversas respuestas y comentarios, aquí hay una versión más insertada que puede agregar a una clase de ayuda:

public static boolean isAppInForeground(Context context) {
  List<RunningTaskInfo> task =
      ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
          .getRunningTasks(1);
  if (task.isEmpty()) {
    return false;
  }
  return task
      .get(0)
      .topActivity
      .getPackageName()
      .equalsIgnoreCase(context.getPackageName());
}

Como se menciona en otras respuestas, debe agregar el siguiente permiso a su AndroidManifest.xml.

<uses-permission android:name="android.permission.GET_TASKS"/>
Pierre-Antoine
fuente
En Android Lollipop este permiso está obsoleto
Mussa
1

Me gustaría agregar que una forma más segura de hacer esto, que verificar si su aplicación está en segundo plano antes de crear una notificación, es simplemente deshabilitar y habilitar el receptor de transmisión onPause () y onResume () respectivamente.

Este método le brinda más control sobre la lógica de la aplicación real y no es probable que cambie en el futuro.

@Override
protected void onPause() {
    unregisterReceiver(mHandleMessageReceiver);
    super.onPause();
}

@Override
protected void onResume() {
    super.onResume();
    registerReceiver(mHandleMessageReceiver, new IntentFilter(DISPLAY_MESSAGE_ACTION));
}
d4c0d312
fuente
1

Encontré una forma más simple y precisa de verificar si la aplicación está en primer plano o en segundo plano asignando las actividades a booleanos.

Consulte la esencia completa aquí

mipreamble
fuente
1

Aquí está el código para una solución simple y agradable descrita anteriormente por @ user2690455. Aunque parece un poco detallado, verá que en general es bastante ligero

En mi caso también usamos AppCompatActivity, así que tenía que tener 2 clases base.

public class BaseActivity extends Activity {

    /**
     * Let field be set only in base class
     * All callers must use accessors,
     * and then it's not up to them to manage state.
     *
     * Making it static since ..
     * 1. It needs to be used across two base classes
     * 2. It's a singleton state in the app
     */
    private static boolean IS_APP_IN_BACKGROUND = false;

    @Override
    protected void onResume() {
        super.onResume();

        BaseActivity.onResumeAppTracking(this);

        BaseActivity.setAppInBackgroundFalse();
    }

    @Override
    protected void onStop() {
        super.onStop();

        BaseActivity.setAppInBackgroundTrue();
    }

    @Override
    protected void onPause() {
        super.onPause();

        BaseActivity.setAppInBackgroundFalse();
    }

    protected static void onResumeAppTracking(Activity activity) {

        if (BaseActivity.isAppInBackground()) {

            // do requirements for returning app to foreground
        }

    }

    protected static void setAppInBackgroundFalse() {

        IS_APP_IN_BACKGROUND = false;
    }

    protected static void setAppInBackgroundTrue() {

        IS_APP_IN_BACKGROUND = true;
    }

    protected static boolean isAppInBackground() {

        return IS_APP_IN_BACKGROUND;
    }
}
Gene Bo
fuente
1

Esto es útil solo cuando desea realizar alguna acción justo cuando comienza su actividad y es donde desea verificar si la aplicación está en primer plano o en segundo plano.

En lugar de usar el Administrador de actividades, hay un truco simple que puede hacer a través del código. Si observa el ciclo de actividad de cerca, el flujo entre dos actividades y el primer plano al fondo es el siguiente. Suponga que A y B son dos actividades.

Cuando la transición de A a B: 1. onPause () de A se llama 2. onResume () de B se llama 3. onStop () de A se llama cuando B se reanuda completamente

Cuando la aplicación pasa a segundo plano: 1. onPause () de A se llama 2. onStop () de A se llama

Puede detectar su evento de fondo simplemente poniendo una bandera en actividad.

Haga una actividad abstracta y extiéndala de sus otras actividades, de modo que no tenga que copiar y pegar el código para todas las demás actividades donde necesite un evento de fondo.

En la actividad abstracta, cree la bandera isAppInBackground.

En el método onCreate (): isAppInBackground = false;

En el método onPause (): isAppInBackground = false;

En el método onStop (): isAppInBackground = true;

Solo necesita verificar en su onResume () si isAppInBackground es verdadero. n después de verificar su bandera, vuelva a configurar isAppInBackground = false

Para la transición entre dos actividades, ya que onSTop () de la primera siempre se llamará después de que se reanude la segunda actividad, el indicador nunca será verdadero y cuando la aplicación esté en segundo plano, se llamará a onStop () de la actividad inmediatamente después de onPause y, por lo tanto, el indicador será verdadero cuando abre la aplicación más tarde.

Sin embargo, hay un escenario más en este enfoque. Si alguna de las pantallas de tu aplicación ya está abierta y pones el móvil inactivo, después de un tiempo, el móvil entrará en modo de suspensión y cuando lo desbloquees, se tratará en un evento de fondo.

Ankita
fuente
1

Aquí hay un método que utilizo (y un método de apoyo):

private boolean checkIfAppIsRunningInForeground() {
    ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
    for(ActivityManager.RunningAppProcessInfo appProcessInfo : activityManager.getRunningAppProcesses()) {
        if(appProcessInfo.processName.contains(this.getPackageName())) {
            return checkIfAppIsRunningInForegroundByAppImportance(appProcessInfo.importance);
        }
    }
    return false;
}

private boolean checkIfAppIsRunningInForegroundByAppImportance(int appImportance) {
    switch (appImportance) {
        //user is aware of app
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE:
            return true;
        //user is not aware of app
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE:
        case ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE:
        default:
            return false;
    }
}
El droide Chris
fuente
Parece que voté por una solución que se publicó hace dos años. Este método ya no funciona después de varias actualizaciones de seguridad en Android.
Droid Chris
0

No hay devolución de llamada global para esto, pero para cada actividad es onStop (). No necesitas meterte con un int atómico. Solo tenga un int global con el número de actividades iniciadas, en cada actividad increméntelo en onStart () y decremítelo en onStop ().

Sigue esto

Kishan Vaghela
fuente
0
     public static boolean isAppRunning(Context context) {

 // check with the first task(task in the foreground)
 // in the returned list of tasks

   ActivityManager activityManager = (ActivityManager)
   context.getSystemService(Context.ACTIVITY_SERVICE);
 List<RunningTaskInfo> services =
 activityManager.getRunningTasks(Integer.MAX_VALUE);
     if
     (services.get(0).topActivity.getPackageName().toString().equalsIgnoreCase(context.getPackageName().toString()))
     {
     return true;
     }
     return false;
     }
Ashish Soni
fuente
getRunningTasks ya no es una opción: el modelo de seguridad más nuevo hace que esto sea prácticamente inútil y está obsoleto.
Tony Maro
@AnaghHegde Vea la respuesta anterior de Gadenkan extrayendo la lista de actividades en ejecución del sistema.
Tony Maro
0

Los enfoques anteriores mencionados aquí no son óptimos. El enfoque basado en tareas requiere un permiso que podría no ser deseado y el enfoque "booleano" es propenso a errores de modificación simultánea.

El enfoque que uso y que (creo) funciona bastante bien en la mayoría de los casos:

Tener una clase "MainApplication" para realizar un seguimiento del recuento de actividades en AtomicInteger :

import android.app.Application;

import java.util.concurrent.atomic.AtomicInteger;

public class MainApplication extends Application {
    static class ActivityCounter {
        private static AtomicInteger ACTIVITY_COUNT = new AtomicInteger(0);

        public static boolean isAppActive() {
            return ACTIVITY_COUNT.get() > 0;
        }

        public static void activityStarted() {
            ACTIVITY_COUNT.incrementAndGet();
        }

        public static void activityStopped() {
            ACTIVITY_COUNT.decrementAndGet();
        }
    }
}

Y cree una clase de actividad base que otras actividades extenderían:

import android.app.Activity;
import android.support.annotation.CallSuper;

public class TestActivity extends Activity {
    @Override
    @CallSuper
    protected void onStart() {
        MainApplication.ActivityCounter.activityStarted();
        super.onStart();
    }

    @Override
    @CallSuper
    protected void onStop() {
        MainApplication.ActivityCounter.activityStopped();
        super.onStop();
    }
}
Tadas Šubonis
fuente
De acuerdo ... pero considere usar ACTIVITY_COUNT.decrementAndGet (); en activityStopped ().
ChrisCarneiro