Merge "profcollect: Move periodic trace worker to the system server" into main
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4be7928..201e0c8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8980,7 +8980,11 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
-        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ProfcollectBGJobService"
+        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$PeriodicTraceJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
+        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ReportProcessJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 228e32e..c31594a 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -16,6 +16,9 @@
 
 package com.android.server.profcollect;
 
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
+
 import android.Manifest;
 import android.annotation.RequiresPermission;
 import android.app.job.JobInfo;
@@ -32,6 +35,7 @@
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -70,10 +74,11 @@
     private int mUsageSetting;
     private boolean mUploadEnabled;
 
-    private static boolean sVerityEnforced;
-    private boolean mAdbActive;
+    static boolean sVerityEnforced;
+    static boolean sIsInteractive;
+    static boolean sAdbActive;
 
-    private IProfCollectd mIProfcollect;
+    private static IProfCollectd sIProfcollect;
     private static ProfcollectForwardingService sSelfService;
     private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
 
@@ -86,17 +91,24 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) {
+            if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+                Log.d(LOG_TAG, "Received broadcast that the device became interactive, was "
+                        + sIsInteractive);
+                sIsInteractive = true;
+            } else if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                Log.d(LOG_TAG, "Received broadcast that the device became noninteractive, was "
+                        + sIsInteractive);
+                sIsInteractive = false;
+            } else if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) {
                 Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
                 createAndUploadReport(sSelfService);
-            }
-            if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
+            } else if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
                 boolean isADB = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false);
                 if (isADB) {
                     boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
                     Log.d(LOG_TAG, "Received broadcast that ADB became " + connected
-                            + ", was " + mAdbActive);
-                    mAdbActive = connected;
+                            + ", was " + sAdbActive);
+                    sAdbActive = connected;
                 }
             }
         }
@@ -129,6 +141,8 @@
             context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);
 
         final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_SCREEN_ON);
+        filter.addAction(ACTION_SCREEN_OFF);
         filter.addAction(INTENT_UPLOAD_PROFILES);
         filter.addAction(UsbManager.ACTION_USB_STATE);
         context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
@@ -153,14 +167,24 @@
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             UsbManager usbManager = getContext().getSystemService(UsbManager.class);
             if (usbManager == null) {
-                mAdbActive = false;
-                return;
+                sAdbActive = false;
+                Log.d(LOG_TAG, "USBManager is not ready");
+            } else {
+                sAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1);
+                Log.d(LOG_TAG, "ADB is " + sAdbActive + " on system startup");
             }
-            mAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1);
-            Log.d(LOG_TAG, "ADB is " + mAdbActive + " on system startup");
+
+            PowerManager powerManager = getContext().getSystemService(PowerManager.class);
+            if (powerManager == null) {
+                sIsInteractive = true;
+                Log.d(LOG_TAG, "PowerManager is not ready");
+            } else {
+                sIsInteractive = powerManager.isInteractive();
+                Log.d(LOG_TAG, "Device is interactive " + sIsInteractive + " on system startup");
+            }
         }
         if (phase == PHASE_BOOT_COMPLETED) {
-            if (mIProfcollect == null) {
+            if (sIProfcollect == null) {
                 return;
             }
             BackgroundThread.get().getThreadHandler().post(() -> {
@@ -172,22 +196,22 @@
     }
 
     private void registerProviderStatusCallback() {
-        if (mIProfcollect == null) {
+        if (sIProfcollect == null) {
             return;
         }
         try {
-            mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
+            sIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
         } catch (RemoteException e) {
             Log.e(LOG_TAG, "Failed to register provider status callback: " + e.getMessage());
         }
     }
 
     private boolean serviceHasSupportedTraceProvider() {
-        if (mIProfcollect == null) {
+        if (sIProfcollect == null) {
             return false;
         }
         try {
-            return !mIProfcollect.get_supported_provider().isEmpty();
+            return !sIProfcollect.get_supported_provider().isEmpty();
         } catch (RemoteException e) {
             Log.e(LOG_TAG, "Failed to get supported provider: " + e.getMessage());
             return false;
@@ -209,7 +233,7 @@
                     IProfCollectd.Stub.asInterface(
                             ServiceManager.getServiceOrThrow("profcollectd"));
             profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0);
-            mIProfcollect = profcollectd;
+            sIProfcollect = profcollectd;
             return true;
         } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
             Log.w(LOG_TAG, "Failed to connect profcollectd binder service.");
@@ -233,7 +257,8 @@
                     break;
                 case MESSAGE_REGISTER_SCHEDULERS:
                     registerObservers();
-                    ProfcollectBGJobService.schedule(getContext());
+                    PeriodicTraceJobService.schedule(getContext());
+                    ReportProcessJobService.schedule(getContext());
                     break;
                 default:
                     throw new AssertionError("Unknown message: " + message);
@@ -246,27 +271,66 @@
         public void binderDied() {
             Log.w(LOG_TAG, "profcollectd has died");
 
-            mIProfcollect = null;
+            sIProfcollect = null;
             tryConnectNativeService();
         }
     }
 
     /**
-     * Background trace process service.
+     * Background report process and upload service.
      */
-    public static class ProfcollectBGJobService extends JobService {
-        // Unique ID in system service
-        private static final int JOB_IDLE_PROCESS = 260817;
+    public static class PeriodicTraceJobService extends JobService {
+        // Unique ID in system server
+        private static final int PERIODIC_TRACE_JOB_ID = 241207;
         private static final ComponentName JOB_SERVICE_NAME = new ComponentName(
                 "android",
-                ProfcollectBGJobService.class.getName());
+                PeriodicTraceJobService.class.getName());
+
+        /**
+         * Attach the service to the system job scheduler.
+         */
+        public static void schedule(Context context) {
+            final int interval = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+                    "collection_interval", 600);
+            JobScheduler js = context.getSystemService(JobScheduler.class);
+            js.schedule(new JobInfo.Builder(PERIODIC_TRACE_JOB_ID, JOB_SERVICE_NAME)
+                    .setPeriodic(TimeUnit.SECONDS.toMillis(interval))
+                    // PRIORITY_DEFAULT is the highest priority we can request for a periodic job.
+                    .setPriority(JobInfo.PRIORITY_DEFAULT)
+                    .build());
+        }
+
+        @Override
+        public boolean onStartJob(JobParameters params) {
+            if (sIProfcollect != null) {
+                Utils.traceSystem(sIProfcollect, "periodic");
+            }
+            jobFinished(params, false);
+            return true;
+        }
+
+        @Override
+        public boolean onStopJob(JobParameters params) {
+            return false;
+        }
+    }
+
+    /**
+     * Background report process and upload service.
+     */
+    public static class ReportProcessJobService extends JobService {
+        // Unique ID in system server
+        private static final int REPORT_PROCESS_JOB_ID = 260817;
+        private static final ComponentName JOB_SERVICE_NAME = new ComponentName(
+                "android",
+                ReportProcessJobService.class.getName());
 
         /**
          * Attach the service to the system job scheduler.
          */
         public static void schedule(Context context) {
             JobScheduler js = context.getSystemService(JobScheduler.class);
-            js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
+            js.schedule(new JobInfo.Builder(REPORT_PROCESS_JOB_ID, JOB_SERVICE_NAME)
                     .setRequiresDeviceIdle(true)
                     .setRequiresCharging(true)
                     .setPeriodic(BG_PROCESS_INTERVAL)
@@ -283,7 +347,6 @@
 
         @Override
         public boolean onStopJob(JobParameters params) {
-            // TODO: Handle this?
             return false;
         }
     }
@@ -311,14 +374,8 @@
     private class AppLaunchObserver extends ActivityMetricsLaunchObserver {
         @Override
         public void onIntentStarted(Intent intent, long timestampNanos) {
-            if (mIProfcollect == null) {
-                return;
-            }
-            if (mAdbActive) {
-                return;
-            }
             if (Utils.withFrequency("applaunch_trace_freq", 5)) {
-                Utils.traceSystem(mIProfcollect, "applaunch");
+                Utils.traceSystem(sIProfcollect, "applaunch");
             }
         }
     }
@@ -336,15 +393,9 @@
     }
 
     private void traceOnDex2oatStart() {
-        if (mIProfcollect == null) {
-            return;
-        }
-        if (mAdbActive) {
-            return;
-        }
         if (Utils.withFrequency("dex2oat_trace_freq", 25)) {
             // Dex2oat could take a while before it starts. Add a short delay before start tracing.
-            Utils.traceSystem(mIProfcollect, "dex2oat", /* delayMs */ 1000);
+            Utils.traceSystem(sIProfcollect, "dex2oat", /* delayMs */ 1000);
         }
     }
 
@@ -367,12 +418,12 @@
 
     private static void createAndUploadReport(ProfcollectForwardingService pfs) {
         BackgroundThread.get().getThreadHandler().post(() -> {
-            if (pfs.mIProfcollect == null) {
+            if (pfs.sIProfcollect == null) {
                 return;
             }
             String reportName;
             try {
-                reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip";
+                reportName = pfs.sIProfcollect.report(pfs.mUsageSetting) + ".zip";
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "Failed to create report: " + e.getMessage());
                 return;
@@ -411,7 +462,7 @@
                     return;
                 }
                 if (Utils.withFrequency("camera_trace_freq", 10)) {
-                    Utils.traceProcess(mIProfcollect,
+                    Utils.traceProcess(sIProfcollect,
                             "camera",
                             "android.hardware.camera.provider",
                             /* durationMs */ 5000);
diff --git a/services/profcollect/src/com/android/server/profcollect/Utils.java b/services/profcollect/src/com/android/server/profcollect/Utils.java
index a8016a0..b754ca1 100644
--- a/services/profcollect/src/com/android/server/profcollect/Utils.java
+++ b/services/profcollect/src/com/android/server/profcollect/Utils.java
@@ -28,28 +28,29 @@
 import java.time.Instant;
 import java.util.concurrent.ThreadLocalRandom;
 
-public final class Utils {
+final class Utils {
 
     private static Instant lastTraceTime = Instant.EPOCH;
     private static final int TRACE_COOLDOWN_SECONDS = 30;
 
-    public static boolean withFrequency(String configName, int defaultFrequency) {
+    static boolean withFrequency(String configName, int defaultFrequency) {
         int threshold = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, configName, defaultFrequency);
         int randomNum = ThreadLocalRandom.current().nextInt(100);
         return randomNum < threshold;
     }
 
-    public static boolean traceSystem(IProfCollectd mIProfcollect, String eventName) {
-        if (mIProfcollect == null) {
-            return false;
-        }
-        if (isInCooldownOrReset()) {
+    /**
+     * Request a system-wide trace.
+     * Will be ignored if the device does not meet trace criteria or is being rate limited.
+     */
+    static boolean traceSystem(IProfCollectd iprofcollectd, String eventName) {
+        if (!checkPrerequisites(iprofcollectd)) {
             return false;
         }
         BackgroundThread.get().getThreadHandler().post(() -> {
             try {
-                mIProfcollect.trace_system(eventName);
+                iprofcollectd.trace_system(eventName);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
             }
@@ -57,16 +58,17 @@
         return true;
     }
 
-    public static boolean traceSystem(IProfCollectd mIProfcollect, String eventName, int delayMs) {
-        if (mIProfcollect == null) {
-            return false;
-        }
-        if (isInCooldownOrReset()) {
+    /**
+     * Request a system-wide trace after a delay.
+     * Will be ignored if the device does not meet trace criteria or is being rate limited.
+     */
+    static boolean traceSystem(IProfCollectd iprofcollectd, String eventName, int delayMs) {
+        if (!checkPrerequisites(iprofcollectd)) {
             return false;
         }
         BackgroundThread.get().getThreadHandler().postDelayed(() -> {
             try {
-                mIProfcollect.trace_system(eventName);
+                iprofcollectd.trace_system(eventName);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
             }
@@ -74,17 +76,18 @@
         return true;
     }
 
-    public static boolean traceProcess(IProfCollectd mIProfcollect,
+    /**
+     * Request a single-process trace.
+     * Will be ignored if the device does not meet trace criteria or is being rate limited.
+     */
+    static boolean traceProcess(IProfCollectd iprofcollectd,
             String eventName, String processName, int durationMs) {
-        if (mIProfcollect == null) {
-            return false;
-        }
-        if (isInCooldownOrReset()) {
+        if (!checkPrerequisites(iprofcollectd)) {
             return false;
         }
         BackgroundThread.get().getThreadHandler().post(() -> {
             try {
-                mIProfcollect.trace_process(eventName,
+                iprofcollectd.trace_process(eventName,
                         processName,
                         durationMs);
             } catch (RemoteException | ServiceSpecificException e) {
@@ -105,4 +108,16 @@
         }
         return true;
     }
+
+    private static boolean checkPrerequisites(IProfCollectd iprofcollectd) {
+        if (iprofcollectd == null) {
+            return false;
+        }
+        if (isInCooldownOrReset()) {
+            return false;
+        }
+        return ProfcollectForwardingService.sVerityEnforced
+            && !ProfcollectForwardingService.sAdbActive
+            && ProfcollectForwardingService.sIsInteractive;
+    }
 }