1 /**
2  * Copyright (C) 2020 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.server.profcollect;
18 
19 import android.app.job.JobInfo;
20 import android.app.job.JobParameters;
21 import android.app.job.JobScheduler;
22 import android.app.job.JobService;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.hardware.camera2.CameraManager;
29 import android.os.Handler;
30 import android.os.IBinder.DeathRecipient;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.SystemProperties;
35 import android.os.UpdateEngine;
36 import android.os.UpdateEngineCallback;
37 import android.provider.DeviceConfig;
38 import android.provider.Settings;
39 import android.provider.Settings.SettingNotFoundException;
40 import android.util.Log;
41 
42 import com.android.internal.R;
43 import com.android.internal.os.BackgroundThread;
44 import com.android.server.IoThread;
45 import com.android.server.LocalManagerRegistry;
46 import com.android.server.LocalServices;
47 import com.android.server.SystemService;
48 import com.android.server.art.ArtManagerLocal;
49 import com.android.server.wm.ActivityMetricsLaunchObserver;
50 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
51 import com.android.server.wm.ActivityTaskManagerInternal;
52 
53 import java.util.concurrent.ThreadLocalRandom;
54 import java.util.concurrent.TimeUnit;
55 
56 /**
57  * System-server-local proxy into the {@code IProfcollectd} native service.
58  */
59 public final class ProfcollectForwardingService extends SystemService {
60     public static final String LOG_TAG = "ProfcollectForwardingService";
61 
62     private static final String INTENT_UPLOAD_PROFILES =
63             "com.android.server.profcollect.UPLOAD_PROFILES";
64     private static final long BG_PROCESS_INTERVAL = TimeUnit.HOURS.toMillis(4); // every 4 hours.
65 
66     private int mUsageSetting;
67     private boolean mUploadEnabled;
68 
69     private IProfCollectd mIProfcollect;
70     private static ProfcollectForwardingService sSelfService;
71     private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
72 
73     private IProviderStatusCallback mProviderStatusCallback = new IProviderStatusCallback.Stub() {
74         public void onProviderReady() {
75             mHandler.sendEmptyMessage(ProfcollectdHandler.MESSAGE_REGISTER_SCHEDULERS);
76         }
77     };
78 
79     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
80         @Override
81         public void onReceive(Context context, Intent intent) {
82             if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) {
83                 Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
84                 createAndUploadReport(sSelfService);
85             }
86         }
87     };
88 
ProfcollectForwardingService(Context context)89     public ProfcollectForwardingService(Context context) {
90         super(context);
91 
92         if (sSelfService != null) {
93             throw new AssertionError("only one service instance allowed");
94         }
95         sSelfService = this;
96 
97         // Get "Usage & diagnostics" checkbox status. 1 is for enabled, 0 is for disabled.
98         try {
99             mUsageSetting = Settings.Global.getInt(context.getContentResolver(), "multi_cb");
100         } catch (SettingNotFoundException e) {
101             Log.e(LOG_TAG, "Usage setting not found: " + e.getMessage());
102             mUsageSetting = -1;
103         }
104 
105         mUploadEnabled =
106             context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);
107 
108         final IntentFilter filter = new IntentFilter();
109         filter.addAction(INTENT_UPLOAD_PROFILES);
110         context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
111     }
112 
113     /**
114      * Check whether profcollect is enabled through device config.
115      */
enabled()116     public static boolean enabled() {
117         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, "enabled",
118             false) || SystemProperties.getBoolean("persist.profcollectd.enabled_override", false);
119     }
120 
121     @Override
onStart()122     public void onStart() {
123         connectNativeService();
124     }
125 
126     @Override
onBootPhase(int phase)127     public void onBootPhase(int phase) {
128         if (phase == PHASE_BOOT_COMPLETED) {
129             if (mIProfcollect == null) {
130                 return;
131             }
132             BackgroundThread.get().getThreadHandler().post(() -> {
133                 if (serviceHasSupportedTraceProvider()) {
134                     registerProviderStatusCallback();
135                 }
136             });
137         }
138     }
139 
registerProviderStatusCallback()140     private void registerProviderStatusCallback() {
141         if (mIProfcollect == null) {
142             return;
143         }
144         try {
145             mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
146         } catch (RemoteException e) {
147             Log.e(LOG_TAG, "Failed to register provider status callback: " + e.getMessage());
148         }
149     }
150 
serviceHasSupportedTraceProvider()151     private boolean serviceHasSupportedTraceProvider() {
152         if (mIProfcollect == null) {
153             return false;
154         }
155         try {
156             return !mIProfcollect.get_supported_provider().isEmpty();
157         } catch (RemoteException e) {
158             Log.e(LOG_TAG, "Failed to get supported provider: " + e.getMessage());
159             return false;
160         }
161     }
162 
tryConnectNativeService()163     private boolean tryConnectNativeService() {
164         if (connectNativeService()) {
165             return true;
166         }
167         // Cannot connect to the native service at this time, retry after a short delay.
168         mHandler.sendEmptyMessageDelayed(ProfcollectdHandler.MESSAGE_BINDER_CONNECT, 5000);
169         return false;
170     }
171 
connectNativeService()172     private boolean connectNativeService() {
173         try {
174             IProfCollectd profcollectd =
175                     IProfCollectd.Stub.asInterface(
176                             ServiceManager.getServiceOrThrow("profcollectd"));
177             profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0);
178             mIProfcollect = profcollectd;
179             return true;
180         } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
181             Log.w(LOG_TAG, "Failed to connect profcollectd binder service.");
182             return false;
183         }
184     }
185 
186     private class ProfcollectdHandler extends Handler {
ProfcollectdHandler(Looper looper)187         public ProfcollectdHandler(Looper looper) {
188             super(looper);
189         }
190 
191         public static final int MESSAGE_BINDER_CONNECT = 0;
192         public static final int MESSAGE_REGISTER_SCHEDULERS = 1;
193 
194         @Override
handleMessage(android.os.Message message)195         public void handleMessage(android.os.Message message) {
196             switch (message.what) {
197                 case MESSAGE_BINDER_CONNECT:
198                     connectNativeService();
199                     break;
200                 case MESSAGE_REGISTER_SCHEDULERS:
201                     registerObservers();
202                     ProfcollectBGJobService.schedule(getContext());
203                     break;
204                 default:
205                     throw new AssertionError("Unknown message: " + message);
206             }
207         }
208     }
209 
210     private class ProfcollectdDeathRecipient implements DeathRecipient {
211         @Override
binderDied()212         public void binderDied() {
213             Log.w(LOG_TAG, "profcollectd has died");
214 
215             mIProfcollect = null;
216             tryConnectNativeService();
217         }
218     }
219 
220     /**
221      * Background trace process service.
222      */
223     public static class ProfcollectBGJobService extends JobService {
224         // Unique ID in system service
225         private static final int JOB_IDLE_PROCESS = 260817;
226         private static final ComponentName JOB_SERVICE_NAME = new ComponentName(
227                 "android",
228                 ProfcollectBGJobService.class.getName());
229 
230         /**
231          * Attach the service to the system job scheduler.
232          */
schedule(Context context)233         public static void schedule(Context context) {
234             JobScheduler js = context.getSystemService(JobScheduler.class);
235             js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
236                     .setRequiresDeviceIdle(true)
237                     .setRequiresCharging(true)
238                     .setPeriodic(BG_PROCESS_INTERVAL)
239                     .setPriority(JobInfo.PRIORITY_MIN)
240                     .build());
241         }
242 
243         @Override
onStartJob(JobParameters params)244         public boolean onStartJob(JobParameters params) {
245             createAndUploadReport(sSelfService);
246             jobFinished(params, false);
247             return true;
248         }
249 
250         @Override
onStopJob(JobParameters params)251         public boolean onStopJob(JobParameters params) {
252             // TODO: Handle this?
253             return false;
254         }
255     }
256 
257     // Event observers
registerObservers()258     private void registerObservers() {
259         BackgroundThread.get().getThreadHandler().post(
260                 () -> {
261                     registerAppLaunchObserver();
262                     registerCameraOpenObserver();
263                     registerDex2oatObserver();
264                     registerOTAObserver();
265                 });
266     }
267 
268     private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
registerAppLaunchObserver()269     private void registerAppLaunchObserver() {
270         ActivityTaskManagerInternal atmInternal =
271                 LocalServices.getService(ActivityTaskManagerInternal.class);
272         ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
273                 atmInternal.getLaunchObserverRegistry();
274         launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
275     }
276 
traceOnAppStart(String packageName)277     private void traceOnAppStart(String packageName) {
278         if (mIProfcollect == null) {
279             return;
280         }
281 
282         // Sample for a fraction of app launches.
283         int traceFrequency = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
284                 "applaunch_trace_freq", 2);
285         int randomNum = ThreadLocalRandom.current().nextInt(100);
286         if (randomNum < traceFrequency) {
287             BackgroundThread.get().getThreadHandler().post(() -> {
288                 try {
289                     mIProfcollect.trace_once("applaunch");
290                 } catch (RemoteException e) {
291                     Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
292                 }
293             });
294         }
295     }
296 
297     private class AppLaunchObserver extends ActivityMetricsLaunchObserver {
298         @Override
onIntentStarted(Intent intent, long timestampNanos)299         public void onIntentStarted(Intent intent, long timestampNanos) {
300             traceOnAppStart(intent.getPackage());
301         }
302     }
303 
registerDex2oatObserver()304     private void registerDex2oatObserver() {
305         ArtManagerLocal aml = LocalManagerRegistry.getManager(ArtManagerLocal.class);
306         if (aml == null) {
307             Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
308             return;
309         }
310         aml.setBatchDexoptStartCallback(Runnable::run,
311                 (snapshot, reason, defaultPackages, builder, passedSignal) -> {
312                     traceOnDex2oatStart();
313                 });
314     }
315 
traceOnDex2oatStart()316     private void traceOnDex2oatStart() {
317         if (mIProfcollect == null) {
318             return;
319         }
320         // Sample for a fraction of dex2oat runs.
321         final int traceFrequency =
322             DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
323                 "dex2oat_trace_freq", 25);
324         int randomNum = ThreadLocalRandom.current().nextInt(100);
325         if (randomNum < traceFrequency) {
326             // Dex2oat could take a while before it starts. Add a short delay before start tracing.
327             BackgroundThread.get().getThreadHandler().postDelayed(() -> {
328                 try {
329                     mIProfcollect.trace_once("dex2oat");
330                 } catch (RemoteException e) {
331                     Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
332                 }
333             }, 1000);
334         }
335     }
336 
registerOTAObserver()337     private void registerOTAObserver() {
338         UpdateEngine updateEngine = new UpdateEngine();
339         updateEngine.bind(new UpdateEngineCallback() {
340             @Override
341             public void onStatusUpdate(int status, float percent) {
342                 if (status == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
343                     createAndUploadReport(sSelfService);
344                 }
345             }
346 
347             @Override
348             public void onPayloadApplicationComplete(int errorCode) {
349                 // Ignored
350             }
351         });
352     }
353 
createAndUploadReport(ProfcollectForwardingService pfs)354     private static void createAndUploadReport(ProfcollectForwardingService pfs) {
355         BackgroundThread.get().getThreadHandler().post(() -> {
356             String reportName;
357             try {
358                 reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip";
359             } catch (RemoteException e) {
360                 Log.e(LOG_TAG, "Failed to create report: " + e.getMessage());
361                 return;
362             }
363             if (!pfs.mUploadEnabled) {
364                 Log.i(LOG_TAG, "Upload is not enabled.");
365                 return;
366             }
367             Intent intent = new Intent()
368                     .setPackage("com.android.shell")
369                     .setAction("com.android.shell.action.PROFCOLLECT_UPLOAD")
370                     .putExtra("filename", reportName);
371             pfs.getContext().sendBroadcast(intent);
372         });
373     }
374 
registerCameraOpenObserver()375     private void registerCameraOpenObserver() {
376         CameraManager cm = getContext().getSystemService(CameraManager.class);
377         cm.registerAvailabilityCallback(new CameraManager.AvailabilityCallback() {
378             @Override
379             public void onCameraOpened(String cameraId, String packageId) {
380                 Log.d(LOG_TAG, "Received camera open event from: " + packageId);
381                 // Skip face auth and Android System Intelligence, since they trigger way too
382                 // often.
383                 if (packageId.startsWith("client.pid")
384                         || packageId.equals("com.google.android.as")) {
385                     return;
386                 }
387                 // Sample for a fraction of camera events.
388                 final int traceFrequency =
389                         DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
390                         "camera_trace_freq", 10);
391                 int randomNum = ThreadLocalRandom.current().nextInt(100);
392                 if (randomNum >= traceFrequency) {
393                     return;
394                 }
395                 // Wait for 1s before starting tracing.
396                 BackgroundThread.get().getThreadHandler().postDelayed(() -> {
397                     try {
398                         mIProfcollect.trace_once("camera");
399                     } catch (RemoteException e) {
400                         Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
401                     }
402                 }, 1000);
403             }
404         }, null);
405     }
406 }
407