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