Develop/Java

[Java] 자바 Android UsageStatsManage를 통해 현재 실행중인 앱 확인

JunJangE 2021. 7. 22. 19:55

사용자가 현재 어떠한 앱을 사용하는지 확인하는 방법을 찾던 중 UsageStatsManage를 찾게 되었다.

UsageStatsManage의 구현 방법은 버튼을 누르고 난 후에 백그라운드에서 현재 어떠한 앱을 사용하는 지 체크 후 로그에 띄우는 방식으로 구현해 보았다.

우선 mainfests -> AndroidManifest.xml에서 다른 앱의 정보를 얻기 위한 권한을 설정한다.

    <!--  UsageStatsManager - 다른 앱의 정보를 얻기 위해서 필요한 권환-->
    <!-- 오류가 나는 경우 alt+enter 또는 xmlns:tools="http://schemas.android.com/tools" 을 추가 -->
    <uses-permission
        android:name="android.permission.PACKAGE_USAGE_STATS"
        tools:ignore="ProtectedPermissions" />

activity_main.xml에서는 간단하게 시작 버튼과 종료 버튼만을 만들어 구현한다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/start_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.499" />

    <Button
        android:id="@+id/end_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="49dp"
        android:layout_marginEnd="1dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="@+id/start_button"
        app:layout_constraintTop_toBottomOf="@+id/start_button" />

</androidx.constraintlayout.widget.ConstraintLayout>

다음은 MainActivity 코드이다. 주석을 잘 확인하면서 코드를 적으면 좋을 것 같다.

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.app.AppOpsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.LongSparseArray;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    CheckPackageNameThread checkPackageNameThread;

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



        // 버튼 정의
        Button start_button = findViewById(R.id.start_button);
        Button end_button = findViewById(R.id.end_button);


        // 시작 버튼 이벤트
        start_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // 권환 허용이 안되어 있으면 권환 설정창으로 이동
                if(!checkPermission()) {
                    Intent PermissionIntent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS, Uri.parse("package:" + getPackageName()));
                    startActivity(PermissionIntent);
                }
                // 권환 허용 되어 있으면 현재 포그라운드 앱 패키지 로그로 띄운다.
                else{

                    operation = true;
                    checkPackageNameThread = new CheckPackageNameThread();
                    checkPackageNameThread.start();
                }

            }
        });

        // 종료 버튼 이벤트
        end_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                operation = false;
            }

        });

    }

    // 현재 포그라운드 앱 패키지 로그로 띄우는 함수
    private class CheckPackageNameThread extends Thread{

        public void run(){
            // operation == true 일때만 실행
            while(operation){
                if(!checkPermission())
                    continue;


                // 현재 포그라운드 앱 패키지 이름 가져오기
                System.out.println(getPackageName(getApplicationContext()));
                try {
                    // 2초마다 패키치 이름을 로그창에 출력
                    sleep(2000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 권환 체크
    private boolean checkPermission(){

        boolean granted = false;

        AppOpsManager appOps = (AppOpsManager) getApplicationContext()
                .getSystemService(Context.APP_OPS_SERVICE);

        int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
                android.os.Process.myUid(), getApplicationContext().getPackageName());

        if (mode == AppOpsManager.MODE_DEFAULT) {
            granted = (getApplicationContext().checkCallingOrSelfPermission(
                    android.Manifest.permission.PACKAGE_USAGE_STATS) == PackageManager.PERMISSION_GRANTED);
        }
        else {
            granted = (mode == AppOpsManager.MODE_ALLOWED);
        }

        return granted;
    }


    // 자신의 앱의 최소 타겟을 롤리팝 이전으로 설정
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    // 현재 포그라운드 앱 패키지를 가져오는 함수
    public static String getPackageName(@NonNull Context context) {

        // UsageStatsManager 선언
        UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);

        // 마지막 실행 앱 타임스탬프
        long lastRunAppTimeStamp = 0L;

        // 얼마만큼의 시간동안 수집한 앱의 이름을 가져오는지 정하기 (begin ~ end 까지의 앱 이름을 수집한다)
        final long INTERVAL = 1000 * 60 * 5;
        final long end = System.currentTimeMillis();
        final long begin = end - INTERVAL; // 5분전


        LongSparseArray packageNameMap = new LongSparseArray<>();

        // 수집한 이벤트들을 담기 위한 UsageEvents
        final UsageEvents usageEvents = usageStatsManager.queryEvents(begin, end);

        // 이벤트가 여러개 있을 경우 (최소 존재는 해야 hasNextEvent가 null이 아니니까)
        while (usageEvents.hasNextEvent()) {

            // 현재 이벤트를 가져오기
            UsageEvents.Event event = new UsageEvents.Event();
            usageEvents.getNextEvent(event);

            // 현재 이벤트가 포그라운드 상태라면(현재 화면에 보이는 앱이라면)
            if(isForeGroundEvent(event)) {

                // 해당 앱 이름을 packageNameMap에 넣는다.
                packageNameMap.put(event.getTimeStamp(), event.getPackageName());

                // 가장 최근에 실행 된 이벤트에 대한 타임스탬프를 업데이트 해준다.
                if(event.getTimeStamp() > lastRunAppTimeStamp) {
                    lastRunAppTimeStamp = event.getTimeStamp();
                }
            }
        }
        // 가장 마지막까지 있는 앱의 이름을 리턴해준다.
        return packageNameMap.get(lastRunAppTimeStamp, "").toString();
    }

    // 앱이 포그라운드 상태인지 체크
    private static boolean isForeGroundEvent(UsageEvents.Event event) {

        // 이벤트가 없으면 false 반환
        if(event == null)
            return false;

        // 이벤트가 포그라운드 상태라면 true 반환
        if(BuildConfig.VERSION_CODE >= 29)
            return event.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED;

        return event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND;
    }

}

코드 흐름을 보게되면 권한을 체크하고 권한 설정이 안되어 있으면 권한 설정 창을 띄어 권한을 허용할건지 체크하고 권한이 허용되어 있으면 현재 포그라운드 앱 패키지 이름을 로그로 띄운다.

위 코드를 다 적고 실행시키면 다음 사진과 같이 2초 간격으로 사용자가 들어간 앱 패키지 이름을 로그로 나오는 것을 확인할 수 있다.

참고

 

안드로이드 화면에 실행중인 앱 확인하는 방법 | 커피한잔의 여유와 코딩

안드로이드 5(롤리팝) 이후부터는 안드로이드 기기에 실행중인 앱을 알 수 없게 되었습니다. 그래서, 안드로이드 5 이후 버전에서는 기기에 저장하고 있는 통계 데이터를 기반으로 완전히 정확

sjava.net

 

 

UsageStatsManager  |  Android 개발자  |  Android Developers

 

developer.android.com

 

github

 

GitHub - junjange/Java-Learning: 자바 학습

자바 학습. Contribute to junjange/Java-Learning development by creating an account on GitHub.

github.com