1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.managedprovisioning.analytics;
18 
19 import static com.android.internal.util.Preconditions.checkNotNull;
20 
21 import android.app.admin.DevicePolicyEventLogger;
22 import android.app.job.JobParameters;
23 import android.app.job.JobService;
24 import android.os.AsyncTask;
25 import android.os.PersistableBundle;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.managedprovisioning.DevicePolicyProtos.DevicePolicyEvent;
29 import com.android.managedprovisioning.common.ProvisionLogger;
30 
31 
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 
37 /**
38  * A {@link JobService} that reads the logs from the {@link InputStream} written to by
39  * {@link DeferredMetricsWriter} and writes them using another {@link MetricsWriter}.
40  *
41  * @see DeferredMetricsWriter
42  */
43 public class ProcessMetricsJobService extends JobService {
44 
45     static String EXTRA_FILE_PATH = "extra_file_path";
46 
47     private final MetricsWriter mMetricsWriter;
48 
49     @VisibleForTesting
ProcessMetricsJobService(MetricsWriter metricsWriter)50     ProcessMetricsJobService(MetricsWriter metricsWriter) {
51         mMetricsWriter = metricsWriter;
52     }
53 
ProcessMetricsJobService()54     public ProcessMetricsJobService() {
55         this(new InstantMetricsWriter());
56     }
57 
58     @Override
onStartJob(JobParameters params)59     public boolean onStartJob(JobParameters params) {
60         final PersistableBundle extras = params.getExtras();
61         if (extras == null || !extras.containsKey(EXTRA_FILE_PATH)) {
62             return false;
63         }
64         final File metrics = new File(extras.getString(EXTRA_FILE_PATH));
65         if (!metrics.exists()) {
66             return false;
67         }
68         executeReadDeferredMetrics(params, metrics);
69         return true;
70     }
71 
72     @VisibleForTesting
executeReadDeferredMetrics(JobParameters params, File metricsFile)73     void executeReadDeferredMetrics(JobParameters params,
74             File metricsFile) {
75         new ReadDeferredMetricsAsyncTask(params, metricsFile, mMetricsWriter).execute();
76     }
77 
78     @Override
onStopJob(JobParameters params)79     public boolean onStopJob(JobParameters params) {
80         return false;
81     }
82 
83     /**
84      * An {@link AsyncTask} which reads the logs from the {@link File} specified in the constructor
85      * and writes them to the specified {@link MetricsWriter}.
86      *
87      * <p>The {@link File} will be deleted after they are written to the {@link MetricsWriter}.
88      */
89     private class ReadDeferredMetricsAsyncTask extends AsyncTask<Void, Void, Void> {
90         private static final int METRICS_INTERVAL_MILLIS = 10;
91         private final MetricsWriter mMetricsWriter;
92         private final File mFile;
93         private final JobParameters mJobParameters;
94 
ReadDeferredMetricsAsyncTask(JobParameters params, File file, MetricsWriter metricsWriter)95         ReadDeferredMetricsAsyncTask(JobParameters params,
96                 File file,
97                 MetricsWriter metricsWriter) {
98             mFile = checkNotNull(file);
99             mMetricsWriter = metricsWriter;
100             mJobParameters = params;
101         }
102 
103         @Override
doInBackground(Void... voids)104         protected Void doInBackground(Void... voids) {
105             try (InputStream inputStream =  new FileInputStream(mFile)) {
106                 DevicePolicyEvent event;
107                 while ((event = DevicePolicyEvent.parseDelimitedFrom(inputStream)) != null) {
108                     delayProcessMetric();
109                     mMetricsWriter.write(devicePolicyEventToLogger(event));
110                 }
111             } catch (IOException e) {
112                 ProvisionLogger.loge(
113                         "Could not parse DevicePolicyEvent while reading from stream.", e);
114             } finally {
115                 mFile.delete();
116             }
117             return null;
118         }
119 
120         @Override
onPostExecute(Void aVoid)121         protected void onPostExecute(Void aVoid) {
122             jobFinished(mJobParameters, false);
123         }
124 
125         /**
126          * Waits for {@link #METRICS_INTERVAL_MILLIS}.
127          * <p>statsd cannot handle too many metrics at once, so we must wait between each
128          * {@link MetricsWriter#write(DevicePolicyEventLogger...)} call.
129          */
delayProcessMetric()130         private void delayProcessMetric() {
131             try {
132                 Thread.sleep(METRICS_INTERVAL_MILLIS);
133             } catch (InterruptedException e) {
134                 ProvisionLogger.loge(
135                         "Thread interrupted while waiting to log metric.", e);
136             }
137         }
138 
devicePolicyEventToLogger(DevicePolicyEvent event)139         private DevicePolicyEventLogger devicePolicyEventToLogger(DevicePolicyEvent event) {
140             final DevicePolicyEventLogger eventLogger = DevicePolicyEventLogger
141                     .createEvent(event.getEventId())
142                     .setAdmin(event.getAdminPackageName())
143                     .setInt(event.getIntegerValue())
144                     .setBoolean(event.getBooleanValue())
145                     .setTimePeriod(event.getTimePeriodMillis());
146             if (event.getStringListValueCount() > 0) {
147                 eventLogger.setStrings(event.getStringListValueList().toArray(new String[0]));
148             }
149             return eventLogger;
150         }
151     }
152 }
153