Android 5.1.1 e superior - getRunningAppProcesses () retorna apenas o pacote do meu aplicativo

100

Parece que o Google finalmente fechou todas as portas para obter o pacote de aplicativos de primeiro plano atual.

Após a atualização do Lollipop, que acabou getRunningTasks(int maxNum)e graças a esta resposta , usei este código para obter o pacote do aplicativo em primeiro plano desde o Lollipop:

final int PROCESS_STATE_TOP = 2;
RunningAppProcessInfo currentInfo = null;
Field field = null;
try {
    field = RunningAppProcessInfo.class.getDeclaredField("processState");
} catch (Exception ignored) { 
}
ActivityManager am = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> appList = am.getRunningAppProcesses();
for (RunningAppProcessInfo app : appList) {
    if (app.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
        app.importanceReasonCode == 0 ) {
        Integer state = null;
        try {
            state = field.getInt( app );
        } catch (Exception ignored) {
        }
        if (state != null && state == PROCESS_STATE_TOP) {
            currentInfo = app;
            break;
        }
    }
}
return currentInfo;

O Android 5.1.1 e superior (6.0 Marshmallow), ao que parece, também matou getRunningAppProcesses(). Ele agora retorna uma lista de seu próprio pacote de aplicativo.


UsageStatsManager

Podemos usar a nova UsageStatsManagerAPI conforme descrito aqui, mas ela não funciona para todos os aplicativos. Alguns aplicativos do sistema retornarão o mesmo pacote

com.google.android.googlequicksearchbox

AccessibilityService (dezembro de 2017: vai ser banido para uso pelo Google)

Alguns aplicativos usam AccessibilityService(como visto aqui ), mas tem algumas desvantagens.


Existe outra maneira de obter o pacote de aplicativos em execução no momento?

Lior Iluz
fonte
1
Du Speed ​​Booster parece funcionar. Não tenho certeza se ele usa UsageStatsManager.
thecr0w
3
Observe que vários fornecedores importantes removeram a atividade do sistema que concede acesso à API UsageStats de seus dispositivos. Isso significa que os aplicativos nesses dispositivos nunca podem adquirir a permissão necessária.
Kevin Krumwiede
3
Samsung sendo um deles. Isso representa mais de 60% do mercado.
Kevin Krumwiede
3
Aqui está um tíquete do rastreador de bug do Android a respeito desse problema: code.google.com/p/android-developer-preview/issues/…
Florian Barth
2
Se eu analisar a saída da execução psem um shell e a política for "fg"e o conteúdo de /proc/[pid]/oom_adj_scoreigual 0, o aplicativo será o aplicativo em primeiro plano. Infelizmente, parece que /proc/[pid]/oom_adj_scorenão é mais legível no Android 6.0. gist.github.com/jaredrummler/7d1498485e584c8a120e
Jared Rummler

Respostas:

93

Para obter uma lista dos processos em execução no Android 1.6 - Android 6.0, você pode usar esta biblioteca que escrevi: https://github.com/jaredrummler/AndroidProcesses A biblioteca lê / proc para obter informações do processo.

O Google restringiu significativamente o acesso a / proc no Android Nougat. Para obter uma lista dos processos em execução no Android Nougat, você precisará usar o UsageStatsManager ou ter acesso root.

Clique no histórico de edição para soluções alternativas anteriores.

Jared Rummler
fonte
4
@androiddeveloper não, eu só usei libsuperuser porque fornece uma maneira fácil de executar um comando shell (não root ou root) e obter a saída. Posso reescrevê-lo sem libsuperuser se houver demanda suficiente.
Jared Rummler
1
Desculpe por isso. Só estava curioso. :(
desenvolvedor Android de
1
@JaredRummler Estou atualmente no processo de implementação de sua solução em meu aplicativo. Pelo que eu posso dizer, parece estar funcionando bem
guy.gc
1
@androiddeveloper, se você deseja classificá-lo com base em um atributo diferente, Processimplemente Comparablee substitua o compareTométodo. Então, quando você usar Collections.sort(processesList), ele usará a ordem que você especificou.
Srini,
2
@BruceWayne Acho que Jared está se referindo a este link: https://source.android.com/devices/tech/security/selinux/index.html
Eduardo Herzer
24
 private String printForegroundTask() {
    String currentApp = "NULL";
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        UsageStatsManager usm = (UsageStatsManager)this.getSystemService("usagestats");
        long time = System.currentTimeMillis();
        List<UsageStats> appList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,  time - 1000*1000, time);
        if (appList != null && appList.size() > 0) {
            SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
            for (UsageStats usageStats : appList) {
                mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
            }
            if (mySortedMap != null && !mySortedMap.isEmpty()) {
                currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
            }
        }
    } else {
        ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> tasks = am.getRunningAppProcesses();
        currentApp = tasks.get(0).processName;
    }

    Log.e("adapter", "Current App in foreground is: " + currentApp);
    return currentApp;
}

Use este método para obter a tarefa de primeiro plano. Você precisará de uma Permissão do Sistema "android: get_usage_stats"

public static boolean needPermissionForBlocking(Context context){
    try {
        PackageManager packageManager = context.getPackageManager();
        ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
        AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
        return  (mode != AppOpsManager.MODE_ALLOWED);
    } catch (PackageManager.NameNotFoundException e) {
        return true;
    }
}

SE o usuário habilitar isso em configuração -> Segurança-> aplicativo com acesso de uso. Depois disso, você receberá a tarefa de primeiro plano. Processo semelhante Clean matser por Cheetahamobile google play link

Tarun Sharma
fonte
Conforme indicado no OP e nos comentários, há grandes desvantagens de uso UsageStatsManager. A Samsung removeu as solicitações e é muito chato para o usuário conceder permissão ao sistema.
Jared Rummler
Eu testei no dispositivo moto e2. Está funcionando bem e sim, precisamos implementar essa permissão. todos nós enfrentamos os mesmos problemas. Todos nós realmente precisamos de uma abordagem melhor. Boa sorte cara
Tarun Sharma
2
Esta abordagem não funciona pelo menos no LG G3 porque não há nenhum item de menu "Configurações -> Segurança-> Aplicativo com acesso de uso"
Dmitry
2
Isso não é uma resposta à minha pergunta. Além disso, nenhuma nova informação é fornecida nesta resposta.
Lior Iluz,
1
Funcionando bem, mas não corretamente em marshmallow. se a notificação veio, o processo em execução atual será a notificação de pacote recebida. na verdade, o aplicativo não está sendo executado em primeiro ou segundo plano :(. precisa de solução.
WonderSoftwares
6

Dê uma olhada em https://github.com/ricvalerio/foregroundappchecker , pode ser o que você precisa. Fornece código de amostra e elimina a dor de ter que implementar o detector de primeiro plano de versão cruzada.

Aqui estão dois exemplos:

AppChecker appChecker = new AppChecker();
String packageName = appChecker.getForegroundApp();

Ou verifique regularmente:

AppChecker appChecker = new AppChecker();
appChecker
    .when("com.other.app", new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .when("com.my.app", new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .other(new AppChecker.Listener() {
        @Override
        public void onForeground(String packageName) {
            // do something
        }
    )
    .timeout(1000)
    .start(this);
rvalerio
fonte
2
Obrigado, mas nada de novo aqui. Ele acabou de embrulhar a API UsageStatsManager, mencionada no OP. O usuário precisará habilitar o acesso, como outros métodos desde Android 5.1.1
Lior Iluz
@ Jérémy você solicitou as permissões necessárias?
rvalerio
Parece que também está pesquisando o tempo todo, certo?
desenvolvedor Android
4

O Google limitou essa funcionalidade apenas para aplicativos do sistema. Conforme relatado em um tíquete de bug , você precisará da permissão REAL_GET_TASKS para acessar lá.

Os aplicativos agora devem ter ... permission.REAL_GET_TASKS para poder obter informações do processo para todos os aplicativos. Apenas as informações do processo para o aplicativo de chamada serão retornadas se o aplicativo não tiver a permissão. Os aplicativos de privilégios poderão obter temporariamente informações do processo para todos os aplicativos se não tiverem a nova permissão, mas tiverem descontinuado ... permission.GET_TASKS Além disso, apenas aplicativos do sistema podem adquirir a permissão REAL_GET_TASKS.

Ilya Gazman
fonte
Eu vi que o applock ainda está funcionando no 5.1.1, uma vez que o aplicativo obtém a permissão em segurança -> "aplicativos com acesso de uso" ... Isso é um tanto surpreendente
mahesh ren
"As permissões com o nível de proteção assinatura, privilégio ou assinaturaOrSystem são concedidas apenas a aplicativos do sistema. Se um aplicativo for um aplicativo normal que não pertence ao sistema, ele nunca será capaz de usar essas permissões." Diz o Android Studio. Boa sorte para negociar com O Google deve ser aceito como um "aplicativo do sistema".
Jérémy
2

Apenas jogando fora uma otimização potencial para o que eu imagino ser um pedaço de código copiado e colado para detectar o aplicativo mais popular no Android M.

este

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
    UsageStatsManager usm = (UsageStatsManager)this.getSystemService("usagestats");
    long time = System.currentTimeMillis();
    List<UsageStats> appList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,  time - 1000*1000, time);
    if (appList != null && appList.size() > 0) {
        SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
        for (UsageStats usageStats : appList) {
            mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
        }
        if (mySortedMap != null && !mySortedMap.isEmpty()) {
            currentApp = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
        }
    }
}

Pode ser simplificado para isso

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
    UsageStatsManager usm = (UsageStatsManager) context.getSystemService(
        Context.USAGE_STATS_SERVICE);
    long time = System.currentTimeMillis();
    List<UsageStats> appStatsList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
            time - 1000 * 1000, time);
    if (appStatsList != null && !appStatsList.isEmpty()) {
        currentApp = Collections.max(appStatsList, (o1, o2) ->
            Long.compare(o1.getLastTimeUsed(), o2.getLastTimeUsed())).getPackageName();
    }
}

Eu me vi usando esse código em um loop de 2 segundos e me perguntei por que estava usando uma solução complexa que era O (n * log (n)) quando uma solução mais simples estava disponível em Collections.max () que é O (n )

bstar55
fonte
0
public class AccessibilityDetectingService extends AccessibilityService {

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

    //Configure these here for compatibility with API 13 and below.

    AccessibilityServiceInfo config = new AccessibilityServiceInfo();
    config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
    config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;

    if (Build.VERSION.SDK_INT >= 16)
        //Just in case this helps
        config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;

    setServiceInfo(config);
}

@Override
public void onAccessibilityEvent(final AccessibilityEvent event) {
        if (event == null ) {
            return;
        } else if(event.getPackageName() == null && event.getClassName() == null){
            return;
        }

            if (activityInfo != null){

                Log.d("CurrentActivity", componentName.flattenToShortString());
        }

}

private ActivityInfo tryGetActivity(ComponentName componentName) {
    try {
        return getPackageManager().getActivityInfo(componentName, 0);
    } catch (PackageManager.NameNotFoundException e) {
        return null;
    }
}
@Override
public void onInterrupt() {
}                
}
}//`enter code here`uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<uses-permission android:name="android.permission.GET_TASKS" />

Em seguida, inicie o serviço e a acessibilidade do aplicativo na configuração do dispositivo-> acessibilidade-> Aplicativo nesse serviço.

Ram Bhawan Kushwaha
fonte
Isso já foi abordado na questão, e você acabou de copiar e colar o código de sua fonte original no StackOverflow.
Sam
-2

Tente usar em getRunningServices()vez do getRunningAppProcesses()método.

 ActivityManager mActivityManager = (ActivityManager) getSy stemService(Context.ACTIVITY_SERVICE);

 List<ActivityManager.RunningServiceInfo> appProcessInfoList = mActivityManager.getRunningServices(Integer.MAX_VALUE);
Joe
fonte
3
Bem-vindo ao Stack Overflow! Não acho que isso funcionará, pois o aplicativo em primeiro plano pode não estar executando um serviço.
Sam