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.os.Handler; 29 import android.os.IBinder.DeathRecipient; 30 import android.os.Looper; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.os.SystemProperties; 34 import android.os.UpdateEngine; 35 import android.os.UpdateEngineCallback; 36 import android.provider.DeviceConfig; 37 import android.provider.Settings; 38 import android.provider.Settings.SettingNotFoundException; 39 import android.util.Log; 40 41 import com.android.internal.R; 42 import com.android.internal.os.BackgroundThread; 43 import com.android.server.IoThread; 44 import com.android.server.LocalServices; 45 import com.android.server.SystemService; 46 import com.android.server.wm.ActivityMetricsLaunchObserver; 47 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry; 48 import com.android.server.wm.ActivityTaskManagerInternal; 49 50 import java.util.concurrent.ThreadLocalRandom; 51 import java.util.concurrent.TimeUnit; 52 53 /** 54 * System-server-local proxy into the {@code IProfcollectd} native service. 55 */ 56 public final class ProfcollectForwardingService extends SystemService { 57 public static final String LOG_TAG = "ProfcollectForwardingService"; 58 59 private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG); 60 private static final String INTENT_UPLOAD_PROFILES = 61 "com.android.server.profcollect.UPLOAD_PROFILES"; 62 private static final long BG_PROCESS_PERIOD = TimeUnit.HOURS.toMillis(4); // every 4 hours. 63 64 private IProfCollectd mIProfcollect; 65 private static ProfcollectForwardingService sSelfService; 66 private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper()); 67 68 private IProviderStatusCallback mProviderStatusCallback = new IProviderStatusCallback.Stub() { 69 public void onProviderReady() { 70 mHandler.sendEmptyMessage(ProfcollectdHandler.MESSAGE_REGISTER_SCHEDULERS); 71 } 72 }; 73 74 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 75 @Override 76 public void onReceive(Context context, Intent intent) { 77 if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) { 78 Log.d(LOG_TAG, "Received broadcast to pack and upload reports"); 79 packAndUploadReport(); 80 } 81 } 82 }; 83 ProfcollectForwardingService(Context context)84 public ProfcollectForwardingService(Context context) { 85 super(context); 86 87 if (sSelfService != null) { 88 throw new AssertionError("only one service instance allowed"); 89 } 90 sSelfService = this; 91 92 final IntentFilter filter = new IntentFilter(); 93 filter.addAction(INTENT_UPLOAD_PROFILES); 94 context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); 95 } 96 97 /** 98 * Check whether profcollect is enabled through device config. 99 */ enabled()100 public static boolean enabled() { 101 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, "enabled", 102 false) || SystemProperties.getBoolean("persist.profcollectd.enabled_override", false); 103 } 104 105 @Override onStart()106 public void onStart() { 107 if (DEBUG) { 108 Log.d(LOG_TAG, "Profcollect forwarding service start"); 109 } 110 connectNativeService(); 111 } 112 113 @Override onBootPhase(int phase)114 public void onBootPhase(int phase) { 115 if (phase == PHASE_BOOT_COMPLETED) { 116 if (mIProfcollect == null) { 117 return; 118 } 119 BackgroundThread.get().getThreadHandler().post(() -> { 120 if (serviceHasSupportedTraceProvider()) { 121 registerProviderStatusCallback(); 122 } 123 }); 124 } 125 } 126 registerProviderStatusCallback()127 private void registerProviderStatusCallback() { 128 if (mIProfcollect == null) { 129 return; 130 } 131 try { 132 mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback); 133 } catch (RemoteException e) { 134 Log.e(LOG_TAG, "Failed to register provider status callback: " + e.getMessage()); 135 } 136 } 137 serviceHasSupportedTraceProvider()138 private boolean serviceHasSupportedTraceProvider() { 139 if (mIProfcollect == null) { 140 return false; 141 } 142 try { 143 return !mIProfcollect.get_supported_provider().isEmpty(); 144 } catch (RemoteException e) { 145 Log.e(LOG_TAG, "Failed to get supported provider: " + e.getMessage()); 146 return false; 147 } 148 } 149 tryConnectNativeService()150 private boolean tryConnectNativeService() { 151 if (connectNativeService()) { 152 return true; 153 } 154 // Cannot connect to the native service at this time, retry after a short delay. 155 mHandler.sendEmptyMessageDelayed(ProfcollectdHandler.MESSAGE_BINDER_CONNECT, 5000); 156 return false; 157 } 158 connectNativeService()159 private boolean connectNativeService() { 160 try { 161 IProfCollectd profcollectd = 162 IProfCollectd.Stub.asInterface( 163 ServiceManager.getServiceOrThrow("profcollectd")); 164 profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0); 165 mIProfcollect = profcollectd; 166 return true; 167 } catch (ServiceManager.ServiceNotFoundException | RemoteException e) { 168 Log.w(LOG_TAG, "Failed to connect profcollectd binder service."); 169 return false; 170 } 171 } 172 173 private class ProfcollectdHandler extends Handler { ProfcollectdHandler(Looper looper)174 public ProfcollectdHandler(Looper looper) { 175 super(looper); 176 } 177 178 public static final int MESSAGE_BINDER_CONNECT = 0; 179 public static final int MESSAGE_REGISTER_SCHEDULERS = 1; 180 181 @Override handleMessage(android.os.Message message)182 public void handleMessage(android.os.Message message) { 183 switch (message.what) { 184 case MESSAGE_BINDER_CONNECT: 185 connectNativeService(); 186 break; 187 case MESSAGE_REGISTER_SCHEDULERS: 188 registerObservers(); 189 ProfcollectBGJobService.schedule(getContext()); 190 break; 191 default: 192 throw new AssertionError("Unknown message: " + message); 193 } 194 } 195 } 196 197 private class ProfcollectdDeathRecipient implements DeathRecipient { 198 @Override binderDied()199 public void binderDied() { 200 Log.w(LOG_TAG, "profcollectd has died"); 201 202 mIProfcollect = null; 203 tryConnectNativeService(); 204 } 205 } 206 207 /** 208 * Background trace process service. 209 */ 210 public static class ProfcollectBGJobService extends JobService { 211 // Unique ID in system service 212 private static final int JOB_IDLE_PROCESS = 260817; 213 private static final ComponentName JOB_SERVICE_NAME = new ComponentName( 214 "android", 215 ProfcollectBGJobService.class.getName()); 216 217 /** 218 * Attach the service to the system job scheduler. 219 */ schedule(Context context)220 public static void schedule(Context context) { 221 JobScheduler js = context.getSystemService(JobScheduler.class); 222 223 js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME) 224 .setRequiresDeviceIdle(true) 225 .setRequiresCharging(true) 226 .setPeriodic(BG_PROCESS_PERIOD) 227 .setPriority(JobInfo.PRIORITY_MIN) 228 .build()); 229 } 230 231 @Override onStartJob(JobParameters params)232 public boolean onStartJob(JobParameters params) { 233 if (DEBUG) { 234 Log.d(LOG_TAG, "Starting background process job"); 235 } 236 237 BackgroundThread.get().getThreadHandler().post( 238 () -> { 239 try { 240 if (sSelfService.mIProfcollect == null) { 241 return; 242 } 243 sSelfService.mIProfcollect.process(); 244 } catch (RemoteException e) { 245 Log.e(LOG_TAG, "Failed to process profiles in background: " 246 + e.getMessage()); 247 } 248 }); 249 return true; 250 } 251 252 @Override onStopJob(JobParameters params)253 public boolean onStopJob(JobParameters params) { 254 // TODO: Handle this? 255 return false; 256 } 257 } 258 259 // Event observers registerObservers()260 private void registerObservers() { 261 BackgroundThread.get().getThreadHandler().post( 262 () -> { 263 registerAppLaunchObserver(); 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 if (DEBUG) { 288 Log.d(LOG_TAG, "Tracing on app launch event: " + packageName); 289 } 290 BackgroundThread.get().getThreadHandler().post(() -> { 291 try { 292 mIProfcollect.trace_once("applaunch"); 293 } catch (RemoteException e) { 294 Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage()); 295 } 296 }); 297 } 298 } 299 300 private class AppLaunchObserver extends ActivityMetricsLaunchObserver { 301 @Override onIntentStarted(Intent intent, long timestampNanos)302 public void onIntentStarted(Intent intent, long timestampNanos) { 303 traceOnAppStart(intent.getPackage()); 304 } 305 } 306 registerOTAObserver()307 private void registerOTAObserver() { 308 UpdateEngine updateEngine = new UpdateEngine(); 309 updateEngine.bind(new UpdateEngineCallback() { 310 @Override 311 public void onStatusUpdate(int status, float percent) { 312 if (DEBUG) { 313 Log.d(LOG_TAG, "Received OTA status update, status: " + status + ", percent: " 314 + percent); 315 } 316 317 if (status == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) { 318 packAndUploadReport(); 319 } 320 } 321 322 @Override 323 public void onPayloadApplicationComplete(int errorCode) { 324 // Ignored 325 } 326 }); 327 } 328 packAndUploadReport()329 private void packAndUploadReport() { 330 if (mIProfcollect == null) { 331 return; 332 } 333 334 Context context = getContext(); 335 BackgroundThread.get().getThreadHandler().post(() -> { 336 try { 337 int usageSetting = -1; 338 try { 339 // Get "Usage & diagnostics" checkbox status. 1 is for enabled, 0 is for 340 // disabled. 341 usageSetting = Settings.Global.getInt(context.getContentResolver(), "multi_cb"); 342 } catch (SettingNotFoundException e) { 343 Log.i(LOG_TAG, "Usage setting not found: " + e.getMessage()); 344 } 345 346 // Prepare profile report 347 String reportName = mIProfcollect.report(usageSetting) + ".zip"; 348 349 if (!context.getResources().getBoolean( 350 R.bool.config_profcollectReportUploaderEnabled)) { 351 Log.i(LOG_TAG, "Upload is not enabled."); 352 return; 353 } 354 355 // Upload the report 356 Intent intent = new Intent() 357 .setPackage("com.android.shell") 358 .setAction("com.android.shell.action.PROFCOLLECT_UPLOAD") 359 .putExtra("filename", reportName); 360 context.sendBroadcast(intent); 361 } catch (RemoteException e) { 362 Log.e(LOG_TAG, "Failed to upload report: " + e.getMessage()); 363 } 364 }); 365 } 366 } 367