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.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.ResolveInfo;
27 import android.os.Handler;
28 import android.os.IBinder.DeathRecipient;
29 import android.os.Looper;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.os.SystemProperties;
33 import android.os.UpdateEngine;
34 import android.os.UpdateEngineCallback;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.provider.DeviceConfig;
38 import android.util.Log;
39 
40 import com.android.internal.R;
41 import com.android.server.IoThread;
42 import com.android.server.LocalServices;
43 import com.android.server.SystemService;
44 import com.android.server.wm.ActivityMetricsLaunchObserver;
45 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
46 import com.android.server.wm.ActivityTaskManagerInternal;
47 
48 import java.nio.file.Files;
49 import java.nio.file.Paths;
50 import java.util.List;
51 import java.util.concurrent.ThreadLocalRandom;
52 import java.util.concurrent.TimeUnit;
53 
54 /**
55  * System-server-local proxy into the {@code IProfcollectd} native service.
56  */
57 public final class ProfcollectForwardingService extends SystemService {
58     public static final String LOG_TAG = "ProfcollectForwardingService";
59 
60     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
61 
62     private static final long BG_PROCESS_PERIOD = TimeUnit.DAYS.toMillis(1); // every 1 day.
63 
64     private IProfCollectd mIProfcollect;
65     private static ProfcollectForwardingService sSelfService;
66     private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
67 
ProfcollectForwardingService(Context context)68     public ProfcollectForwardingService(Context context) {
69         super(context);
70 
71         if (sSelfService != null) {
72             throw new AssertionError("only one service instance allowed");
73         }
74         sSelfService = this;
75     }
76 
77     /**
78      * Check whether profcollect is enabled through device config.
79      */
enabled()80     public static boolean enabled() {
81         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, "enabled",
82             false) || SystemProperties.getBoolean("persist.profcollectd.enabled_override", false);
83     }
84 
85     @Override
onStart()86     public void onStart() {
87         if (DEBUG) {
88             Log.d(LOG_TAG, "Profcollect forwarding service start");
89         }
90         connectNativeService();
91     }
92 
93     @Override
onBootPhase(int phase)94     public void onBootPhase(int phase) {
95         if (phase == PHASE_BOOT_COMPLETED) {
96             if (mIProfcollect == null) {
97                 return;
98             }
99             if (serviceHasSupportedTraceProvider()) {
100                 registerObservers();
101             }
102             ProfcollectBGJobService.schedule(getContext());
103         }
104     }
105 
serviceHasSupportedTraceProvider()106     private boolean serviceHasSupportedTraceProvider() {
107         if (mIProfcollect == null) {
108             return false;
109         }
110         try {
111             return !mIProfcollect.get_supported_provider().isEmpty();
112         } catch (RemoteException e) {
113             Log.e(LOG_TAG, e.getMessage());
114             return false;
115         }
116     }
117 
tryConnectNativeService()118     private boolean tryConnectNativeService() {
119         if (connectNativeService()) {
120             return true;
121         }
122         // Cannot connect to the native service at this time, retry after a short delay.
123         mHandler.sendEmptyMessageDelayed(ProfcollectdHandler.MESSAGE_BINDER_CONNECT, 5000);
124         return false;
125     }
126 
connectNativeService()127     private boolean connectNativeService() {
128         try {
129             IProfCollectd profcollectd =
130                     IProfCollectd.Stub.asInterface(
131                             ServiceManager.getServiceOrThrow("profcollectd"));
132             profcollectd.asBinder().linkToDeath(new ProfcollectdDeathRecipient(), /*flags*/0);
133             mIProfcollect = profcollectd;
134             return true;
135         } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
136             Log.w(LOG_TAG, "Failed to connect profcollectd binder service.");
137             return false;
138         }
139     }
140 
141     private class ProfcollectdHandler extends Handler {
ProfcollectdHandler(Looper looper)142         public ProfcollectdHandler(Looper looper) {
143             super(looper);
144         }
145 
146         public static final int MESSAGE_BINDER_CONNECT = 0;
147 
148         @Override
handleMessage(android.os.Message message)149         public void handleMessage(android.os.Message message) {
150             switch (message.what) {
151                 case MESSAGE_BINDER_CONNECT:
152                     connectNativeService();
153                     break;
154                 default:
155                     throw new AssertionError("Unknown message: " + message.toString());
156             }
157         }
158     }
159 
160     private class ProfcollectdDeathRecipient implements DeathRecipient {
161         @Override
binderDied()162         public void binderDied() {
163             Log.w(LOG_TAG, "profcollectd has died");
164 
165             mIProfcollect = null;
166             tryConnectNativeService();
167         }
168     }
169 
170     /**
171      * Background trace process service.
172      */
173     public static class ProfcollectBGJobService extends JobService {
174         // Unique ID in system service
175         private static final int JOB_IDLE_PROCESS = 260817;
176         private static final ComponentName JOB_SERVICE_NAME = new ComponentName(
177                 "android",
178                 ProfcollectBGJobService.class.getName());
179 
180         /**
181          * Attach the service to the system job scheduler.
182          */
schedule(Context context)183         public static void schedule(Context context) {
184             JobScheduler js = context.getSystemService(JobScheduler.class);
185 
186             js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
187                     .setRequiresDeviceIdle(true)
188                     .setRequiresCharging(true)
189                     .setPeriodic(BG_PROCESS_PERIOD)
190                     .build());
191         }
192 
193         @Override
onStartJob(JobParameters params)194         public boolean onStartJob(JobParameters params) {
195             if (DEBUG) {
196                 Log.d(LOG_TAG, "Starting background process job");
197             }
198 
199             try {
200                 sSelfService.mIProfcollect.process(false);
201             } catch (RemoteException e) {
202                 Log.e(LOG_TAG, e.getMessage());
203             }
204             return true;
205         }
206 
207         @Override
onStopJob(JobParameters params)208         public boolean onStopJob(JobParameters params) {
209             // TODO: Handle this?
210             return false;
211         }
212     }
213 
214     // Event observers
registerObservers()215     private void registerObservers() {
216         registerAppLaunchObserver();
217         registerOTAObserver();
218     }
219 
220     private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
registerAppLaunchObserver()221     private void registerAppLaunchObserver() {
222         ActivityTaskManagerInternal atmInternal =
223                 LocalServices.getService(ActivityTaskManagerInternal.class);
224         ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
225                 atmInternal.getLaunchObserverRegistry();
226         launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
227     }
228 
traceOnAppStart(String packageName)229     private void traceOnAppStart(String packageName) {
230         if (mIProfcollect == null) {
231             return;
232         }
233 
234         // Sample for a fraction of app launches.
235         int traceFrequency = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
236                 "applaunch_trace_freq", 2);
237         int randomNum = ThreadLocalRandom.current().nextInt(100);
238         if (randomNum < traceFrequency) {
239             try {
240                 if (DEBUG) {
241                     Log.d(LOG_TAG, "Tracing on app launch event: " + packageName);
242                 }
243                 mIProfcollect.trace_once("applaunch");
244             } catch (RemoteException e) {
245                 Log.e(LOG_TAG, e.getMessage());
246             }
247         }
248     }
249 
250     private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
251         @Override
onIntentStarted(Intent intent, long timestampNanos)252         public void onIntentStarted(Intent intent, long timestampNanos) {
253             traceOnAppStart(intent.getPackage());
254         }
255 
256         @Override
onIntentFailed()257         public void onIntentFailed() {
258             // Ignored
259         }
260 
261         @Override
onActivityLaunched(byte[] activity, int temperature)262         public void onActivityLaunched(byte[] activity, int temperature) {
263             // Ignored
264         }
265 
266         @Override
onActivityLaunchCancelled(byte[] abortingActivity)267         public void onActivityLaunchCancelled(byte[] abortingActivity) {
268             // Ignored
269         }
270 
271         @Override
onActivityLaunchFinished(byte[] finalActivity, long timestampNanos)272         public void onActivityLaunchFinished(byte[] finalActivity, long timestampNanos) {
273             // Ignored
274         }
275 
276         @Override
onReportFullyDrawn(byte[] activity, long timestampNanos)277         public void onReportFullyDrawn(byte[] activity, long timestampNanos) {
278             // Ignored
279         }
280     }
281 
registerOTAObserver()282     private void registerOTAObserver() {
283         UpdateEngine updateEngine = new UpdateEngine();
284         updateEngine.bind(new UpdateEngineCallback() {
285             @Override
286             public void onStatusUpdate(int status, float percent) {
287                 if (DEBUG) {
288                     Log.d(LOG_TAG, "Received OTA status update, status: " + status + ", percent: "
289                             + percent);
290                 }
291 
292                 if (status == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
293                     packProfileReport();
294                 }
295             }
296 
297             @Override
298             public void onPayloadApplicationComplete(int errorCode) {
299                 // Ignored
300             }
301         });
302     }
303 
packProfileReport()304     private void packProfileReport() {
305         if (mIProfcollect == null) {
306             return;
307         }
308 
309         if (!getUploaderEnabledConfig(getContext())) {
310             return;
311         }
312 
313         new Thread(() -> {
314             try {
315                 Context context = getContext();
316                 final String uploaderPkg = getUploaderPackageName(context);
317                 final String uploaderAction = getUploaderActionName(context);
318                 String reportUuid = mIProfcollect.report();
319 
320                 final int profileId = getBBProfileId();
321                 String reportDir = "/data/user/" + profileId
322                         + "/com.google.android.apps.internal.betterbug/cache/";
323                 String reportPath = reportDir + reportUuid + ".zip";
324 
325                 if (!Files.exists(Paths.get(reportDir))) {
326                     Log.i(LOG_TAG, "Destination directory does not exist, abort upload.");
327                     return;
328                 }
329 
330                 Intent uploadIntent =
331                         new Intent(uploaderAction)
332                         .setPackage(uploaderPkg)
333                         .putExtra("EXTRA_DESTINATION", "PROFCOLLECT")
334                         .putExtra("EXTRA_PACKAGE_NAME", getContext().getPackageName())
335                         .putExtra("EXTRA_PROFILE_PATH", reportPath)
336                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
337 
338                 List<ResolveInfo> receivers =
339                         context.getPackageManager().queryBroadcastReceivers(uploadIntent, 0);
340                 if (receivers == null || receivers.isEmpty()) {
341                     Log.i(LOG_TAG, "No one to receive upload intent, abort upload.");
342                     return;
343                 }
344                 mIProfcollect.copy_report_to_bb(profileId, reportUuid);
345                 context.sendBroadcast(uploadIntent);
346                 mIProfcollect.delete_report(reportUuid);
347             } catch (RemoteException e) {
348                 Log.e(LOG_TAG, e.getMessage());
349             }
350         }).start();
351     }
352 
353     /**
354      * Get BetterBug's profile ID. It is the work profile ID, if it exists. Otherwise the system
355      * user ID.
356      *
357      * @return BetterBug's profile ID.
358      */
getBBProfileId()359     private int getBBProfileId() {
360         UserManager userManager = UserManager.get(getContext());
361         int[] profiles = userManager.getProfileIds(UserHandle.USER_SYSTEM, false);
362         for (int p : profiles) {
363             if (userManager.getUserInfo(p).isManagedProfile()) {
364                 return p;
365             }
366         }
367         return UserHandle.USER_SYSTEM;
368     }
369 
getUploaderEnabledConfig(Context context)370     private boolean getUploaderEnabledConfig(Context context) {
371         return context.getResources().getBoolean(
372             R.bool.config_profcollectReportUploaderEnabled);
373     }
374 
getUploaderPackageName(Context context)375     private String getUploaderPackageName(Context context) {
376         return context.getResources().getString(
377             R.string.config_defaultProfcollectReportUploaderApp);
378     }
379 
getUploaderActionName(Context context)380     private String getUploaderActionName(Context context) {
381         return context.getResources().getString(
382             R.string.config_defaultProfcollectReportUploaderAction);
383     }
384 }
385