Android monitors mobile app usage

brief introduction

In this article, Android is used to obtain the Activity on the top of the mobile phone to monitor the usage of mobile applications

Technical background

In my daily study, I want to automatically collect the use time of various applications of mobile phone software, such as get, geek time, Keep, wechat reading, etc

However, after searching, it was not found that they had the corresponding API development interface, so there was no way to obtain it

Xiaomi's mobile phone has the usage of mobile applications. In the screen usage time, the data looks very good and suitable, but I don't know how to obtain it

Finally, you can only write a native Android application by yourself, report to the server every 10 seconds by obtaining the top-level Activity of the mobile phone, and set the mapping between the Activity and the application name to achieve the purpose of your own mobile application usage statistics

Code details

The complete code is on GitHub: https://github.com/lw1243925457/self_growth_android

Only for code reference. At present, data monitoring and uploading are available, but these interfaces are still very rough and not perfect

You should pay attention to the following points when using this function:

  • 1. It is necessary to set monitoring as a background service to avoid being frequently kill ed after switching to other applications
  • 2. It is necessary to call relevant permission settings to enable users to open relevant applications and obtain permissions
  • 3. The rest is the preparation of application monitoring code

The specific codes are as follows:

Application monitoring - get top-level Activity of mobile phone

We need to create a new background Service, inherit the Service, and then start a timer to obtain the top-level Activity every ten seconds. The data upload part can be ignored

The code is as follows:

public class MonitorActivityService extends Service {

    private String beforeActivity;
    private final ActivityRequest activityRequest = new ActivityRequest();

    /*
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //Processing tasks
        return START_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @SuppressLint("CommitPrefEdits")
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("foreground", "onCreate");
        //If the API is above 26, that is, the version is O, call the startforeround () method to start the service
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            setForegroundService();
        }
    }

    /**
     * Start service by notification
     * 
     * 10 Get once per second
     */
    @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.O)
    public void  setForegroundService()
    {
        //Set notification channel name
        String channelName = "test";
        //Set the importance of notifications
        int importance = NotificationManager.IMPORTANCE_LOW;
        //Build notification channels
        NotificationChannel channel = new NotificationChannel("232", channelName, importance);
        channel.setDescription("test");
        //Send notifications on created notification channels
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "232");
        builder.setSmallIcon(R.drawable.ic_launcher_foreground) //Set notification icon
                .setContentTitle("Monitoring mobile activity and reporting")//Set notification title
                .setContentText("Monitoring mobile activity and reporting")//Set notification content
                .setAutoCancel(true) //Automatically turns off when touched by the user
                .setOngoing(true);//Settings are running
        //Register the notification channel with the system. After registration, the importance and other notification behaviors cannot be changed
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.createNotificationChannel(channel);
        //Place service in startup state NOTIFICATION_ID refers to the ID of the notification created
        startForeground(232,builder.build());

        Handler handler=new Handler();
        Runnable runnable=new Runnable(){
            @Override
            public void run() {
                Log.d("Monitor Detect", "Timed detection top-level application");
                getTopActivity();
                handler.postDelayed(this, 10000);
            }
        };
        handler.postDelayed(runnable, 10000);//Run every two seconds
    }

    /**
     * Get mobile top-level Activity
     */
    public void getTopActivity()
    {
        long endTime = System.currentTimeMillis();
        long beginTime = endTime - 10000;
        UsageStatsManager sUsageStatsManager = (UsageStatsManager) this.getSystemService(Context.USAGE_STATS_SERVICE);
        String result = "";
        UsageEvents.Event event = new UsageEvents.Event();
        UsageEvents usageEvents = sUsageStatsManager.queryEvents(beginTime, endTime);
        while (usageEvents.hasNextEvent()) {
            usageEvents.getNextEvent(event);
            if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
                result = event.getPackageName()+"/"+event.getClassName();
            }
        }
        if (!android.text.TextUtils.isEmpty(result)) {
            Log.d("Service", result);
            beforeActivity = result;
        } else {
            Log.d("Before Service", beforeActivity == null ? "null" : beforeActivity);
        }

        if (beforeActivity == null) {
            Toast.makeText(MonitorActivityService.this.getApplicationContext(),"Activity is empty",Toast.LENGTH_SHORT).show();
            return;
        }

        activityRequest.uploadRecord(beforeActivity, success -> {

        }, failed -> {
            Toast.makeText(MonitorActivityService.this.getApplicationContext(),"Upload failed",Toast.LENGTH_SHORT).show();
            Log.w("Activity", "Upload failed:" + failed);
        });
    }
}

Related permission settings

Relevant permissions need to be enabled in the configuration

At androidmanifest Add to the XML file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.selfgrowth">

    // Application usage permissions
    <permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
    ......

    <queries>
    .......
    </queries>

    <application
    .......
    </application>

</manifest>

Application startup

Add startup logic to MainActivity

public class MainActivity extends AppCompatActivity {

    private AppBarConfiguration mAppBarConfiguration;
    private ActivityMainBinding binding;

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
	......

        // Access to mobile phone usage
        if (!isStatAccessPermissionSet()) {
            Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
            this.startActivity(intent);
        }

        // Android 8.0 uses startforeroundservice to start a new service in the foreground
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        {
            this.startForegroundService(new Intent(MainActivity.this, MonitorActivityService.class));
        }
        else{
            this.startService(new Intent(MainActivity.this, MonitorActivityService.class));
        }

        if (!isNotificationEnabled()) {
            goToNotificationSetting();
        }
    }

    /**
     * Judge whether the permission notice has been authorized
     * When the return value is true, the notification bar is opened and false is not opened.
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private boolean isNotificationEnabled() {

        String CHECK_OP_NO_THROW = "checkOpNoThrow";
        String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";

        AppOpsManager mAppOps = (AppOpsManager) this.getSystemService(Context.APP_OPS_SERVICE);
        ApplicationInfo appInfo = this.getApplicationInfo();
        String pkg = this.getApplicationContext().getPackageName();
        int uid = appInfo.uid;

        Class appOpsClass = null;
        /* Context.APP_OPS_MANAGER */
        try {
            appOpsClass = Class.forName(AppOpsManager.class.getName());
            Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
                    String.class);
            Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);

            int value = (Integer) opPostNotificationValue.get(Integer.class);
            return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);

        } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * Jump to the setting interface of app -- start notification
     */
    private void goToNotificationSetting() {
        Intent intent = new Intent();
        if (Build.VERSION.SDK_INT >= 26) {
            // android 8.0 boot
            intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
            intent.putExtra("android.provider.extra.APP_PACKAGE", this.getPackageName());
        } else if (Build.VERSION.SDK_INT >= 21) {
            // android 5.0-7.0
            intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
            intent.putExtra("app_package", this.getPackageName());
            intent.putExtra("app_uid", this.getApplicationInfo().uid);
        } else {
            // other
            intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
            intent.setData(Uri.fromParts("package", this.getPackageName(), null));
        }
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        this.startActivity(intent);
    }

    /**
     * Determine whether the application permission with permission to view usage has been obtained
     */
    public boolean isStatAccessPermissionSet() {
        try {
            PackageManager packageManager = this.getPackageManager();
            ApplicationInfo info = packageManager.getApplicationInfo(this.getPackageName(), 0);
            AppOpsManager appOpsManager = (AppOpsManager) this.getSystemService(APP_OPS_SERVICE);
            return appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, info.uid, info.packageName) == AppOpsManager.MODE_ALLOWED;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

After you start Android Studio, you can see the relevant output log

Keywords: Java Android Apache

Added by lorne17 on Wed, 16 Feb 2022 22:43:07 +0200