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