1 /*
2  * Copyright (C) 2018 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;
18 
19 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
20 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
21 
22 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_COLLECT_LATENCY_DATA_KEY;
23 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_DETAILED_TRACKING_KEY;
24 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_ENABLED_KEY;
25 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_IGNORE_BATTERY_STATUS_KEY;
26 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_MAX_CALL_STATS_KEY;
27 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_SAMPLING_INTERVAL_KEY;
28 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_SHARDING_MODULO_KEY;
29 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_TRACK_DIRECT_CALLING_UID_KEY;
30 import static com.android.internal.os.BinderCallsStats.SettingsObserver.SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY;
31 
32 import android.app.ActivityThread;
33 import android.content.Context;
34 import android.content.pm.PackageInfo;
35 import android.content.pm.PackageManager;
36 import android.content.pm.PackageManager.NameNotFoundException;
37 import android.database.ContentObserver;
38 import android.net.Uri;
39 import android.os.BatteryStatsInternal;
40 import android.os.Binder;
41 import android.os.ParcelFileDescriptor;
42 import android.os.Process;
43 import android.os.ShellCommand;
44 import android.os.SystemProperties;
45 import android.os.UserHandle;
46 import android.provider.Settings;
47 import android.util.ArrayMap;
48 import android.util.ArraySet;
49 import android.util.KeyValueListParser;
50 import android.util.Slog;
51 
52 import com.android.internal.os.AppIdToPackageMap;
53 import com.android.internal.os.BackgroundThread;
54 import com.android.internal.os.BinderCallsStats;
55 import com.android.internal.os.BinderInternal;
56 import com.android.internal.os.CachedDeviceState;
57 import com.android.internal.util.DumpUtils;
58 
59 import java.io.FileDescriptor;
60 import java.io.PrintWriter;
61 import java.util.ArrayList;
62 import java.util.Collection;
63 import java.util.List;
64 
65 public class BinderCallsStatsService extends Binder {
66 
67     private static final String TAG = "BinderCallsStatsService";
68     private static final String SERVICE_NAME = "binder_calls_stats";
69 
70     private static final String PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING
71             = "persist.sys.binder_calls_detailed_tracking";
72 
73     /** Resolves the work source of an incoming binder transaction. */
74     static class AuthorizedWorkSourceProvider implements BinderInternal.WorkSourceProvider {
75         private ArraySet<Integer> mAppIdTrustlist;
76 
AuthorizedWorkSourceProvider()77         AuthorizedWorkSourceProvider() {
78             mAppIdTrustlist = new ArraySet<>();
79         }
80 
resolveWorkSourceUid(int untrustedWorkSourceUid)81         public int resolveWorkSourceUid(int untrustedWorkSourceUid) {
82             final int callingUid = getCallingUid();
83             final int appId = UserHandle.getAppId(callingUid);
84             if (mAppIdTrustlist.contains(appId)) {
85                 final int workSource = untrustedWorkSourceUid;
86                 final boolean isWorkSourceSet = workSource != Binder.UNSET_WORKSOURCE;
87                 return isWorkSourceSet ?  workSource : callingUid;
88             }
89             return callingUid;
90         }
91 
systemReady(Context context)92         public void systemReady(Context context) {
93             mAppIdTrustlist = createAppidTrustlist(context);
94         }
95 
dump(PrintWriter pw, AppIdToPackageMap packageMap)96         public void dump(PrintWriter pw, AppIdToPackageMap packageMap) {
97             pw.println("AppIds of apps that can set the work source:");
98             final ArraySet<Integer> trustlist = mAppIdTrustlist;
99             for (Integer appId : trustlist) {
100                 pw.println("\t- " + packageMap.mapAppId(appId));
101             }
102         }
103 
getCallingUid()104         protected int getCallingUid() {
105             return Binder.getCallingUid();
106         }
107 
createAppidTrustlist(Context context)108         private ArraySet<Integer> createAppidTrustlist(Context context) {
109             // Use a local copy instead of mAppIdTrustlist to prevent concurrent read access.
110             final ArraySet<Integer> trustlist = new ArraySet<>();
111 
112             // We trust our own process.
113             trustlist.add(UserHandle.getAppId(Process.myUid()));
114             // We only need to initialize it once. UPDATE_DEVICE_STATS is a system permission.
115             final PackageManager pm = context.getPackageManager();
116             final String[] permissions = { android.Manifest.permission.UPDATE_DEVICE_STATS };
117             final int queryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
118             final List<PackageInfo> packages =
119                     pm.getPackagesHoldingPermissions(permissions, queryFlags);
120             final int packagesSize = packages.size();
121             for (int i = 0; i < packagesSize; i++) {
122                 final PackageInfo pkgInfo = packages.get(i);
123                 try {
124                     final int uid = pm.getPackageUid(pkgInfo.packageName, queryFlags);
125                     final int appId = UserHandle.getAppId(uid);
126                     trustlist.add(appId);
127                 } catch (NameNotFoundException e) {
128                     Slog.e(TAG, "Cannot find uid for package name " + pkgInfo.packageName, e);
129                 }
130             }
131             return trustlist;
132         }
133     }
134 
135     /** Listens for flag changes. */
136     private static class SettingsObserver extends ContentObserver {
137         private boolean mEnabled;
138         private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS);
139         private final Context mContext;
140         private final KeyValueListParser mParser = new KeyValueListParser(',');
141         private final BinderCallsStats mBinderCallsStats;
142         private final AuthorizedWorkSourceProvider mWorkSourceProvider;
143 
SettingsObserver(Context context, BinderCallsStats binderCallsStats, AuthorizedWorkSourceProvider workSourceProvider)144         SettingsObserver(Context context, BinderCallsStats binderCallsStats,
145                 AuthorizedWorkSourceProvider workSourceProvider) {
146             super(BackgroundThread.getHandler());
147             mContext = context;
148             context.getContentResolver().registerContentObserver(mUri, false, this,
149                     UserHandle.USER_SYSTEM);
150             mBinderCallsStats = binderCallsStats;
151             mWorkSourceProvider = workSourceProvider;
152             // Always kick once to ensure that we match current state
153             onChange();
154         }
155 
156         @Override
onChange(boolean selfChange, Uri uri, int userId)157         public void onChange(boolean selfChange, Uri uri, int userId) {
158             if (mUri.equals(uri)) {
159                 onChange();
160             }
161         }
162 
onChange()163         public void onChange() {
164             // Do not overwrite the default set manually.
165             if (!SystemProperties.get(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING).isEmpty()) {
166               return;
167             }
168 
169             try {
170                 mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
171                         Settings.Global.BINDER_CALLS_STATS));
172             } catch (IllegalArgumentException e) {
173                 Slog.e(TAG, "Bad binder call stats settings", e);
174             }
175             mBinderCallsStats.setDetailedTracking(mParser.getBoolean(
176                     SETTINGS_DETAILED_TRACKING_KEY, BinderCallsStats.DETAILED_TRACKING_DEFAULT));
177             mBinderCallsStats.setSamplingInterval(mParser.getInt(
178                     SETTINGS_SAMPLING_INTERVAL_KEY,
179                     BinderCallsStats.PERIODIC_SAMPLING_INTERVAL_DEFAULT));
180             mBinderCallsStats.setMaxBinderCallStats(mParser.getInt(
181                     SETTINGS_MAX_CALL_STATS_KEY,
182                     BinderCallsStats.MAX_BINDER_CALL_STATS_COUNT_DEFAULT));
183             mBinderCallsStats.setTrackScreenInteractive(
184                     mParser.getBoolean(SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY,
185                     BinderCallsStats.DEFAULT_TRACK_SCREEN_INTERACTIVE));
186             mBinderCallsStats.setTrackDirectCallerUid(
187                     mParser.getBoolean(SETTINGS_TRACK_DIRECT_CALLING_UID_KEY,
188                     BinderCallsStats.DEFAULT_TRACK_DIRECT_CALLING_UID));
189             mBinderCallsStats.setIgnoreBatteryStatus(
190                     mParser.getBoolean(SETTINGS_IGNORE_BATTERY_STATUS_KEY,
191                     BinderCallsStats.DEFAULT_IGNORE_BATTERY_STATUS));
192             mBinderCallsStats.setShardingModulo(mParser.getInt(
193                     SETTINGS_SHARDING_MODULO_KEY,
194                     BinderCallsStats.SHARDING_MODULO_DEFAULT));
195 
196             mBinderCallsStats.setCollectLatencyData(
197                     mParser.getBoolean(SETTINGS_COLLECT_LATENCY_DATA_KEY,
198                     BinderCallsStats.DEFAULT_COLLECT_LATENCY_DATA));
199             // Binder latency observer settings.
200             BinderCallsStats.SettingsObserver.configureLatencyObserver(
201                     mParser,
202                     mBinderCallsStats.getLatencyObserver());
203 
204             final boolean enabled =
205                     mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
206             if (mEnabled != enabled) {
207                 if (enabled) {
208                     Binder.setObserver(mBinderCallsStats);
209                     Binder.setProxyTransactListener(
210                             new Binder.PropagateWorkSourceTransactListener());
211                     Binder.setWorkSourceProvider(mWorkSourceProvider);
212                 } else {
213                     Binder.setObserver(null);
214                     Binder.setProxyTransactListener(null);
215                     Binder.setWorkSourceProvider((x) -> Binder.getCallingUid());
216                 }
217                 mEnabled = enabled;
218                 mBinderCallsStats.reset();
219                 mBinderCallsStats.setAddDebugEntries(enabled);
220                 mBinderCallsStats.getLatencyObserver().reset();
221             }
222         }
223     }
224 
225     /**
226      * @hide Only for use within the system server.
227      */
228     public static class Internal {
229         private final BinderCallsStats mBinderCallsStats;
230 
Internal(BinderCallsStats binderCallsStats)231         Internal(BinderCallsStats binderCallsStats) {
232             this.mBinderCallsStats = binderCallsStats;
233         }
234 
235         /** @see BinderCallsStats#reset */
reset()236         public void reset() {
237             mBinderCallsStats.reset();
238         }
239 
240         /**
241          * @see BinderCallsStats#getExportedCallStats.
242          *
243          * Note that binder calls stats will be reset by statsd every time
244          * the data is exported.
245          */
getExportedCallStats()246         public ArrayList<BinderCallsStats.ExportedCallStat> getExportedCallStats() {
247             return mBinderCallsStats.getExportedCallStats();
248         }
249 
250         /** @see BinderCallsStats#getExportedExceptionStats */
getExportedExceptionStats()251         public ArrayMap<String, Integer> getExportedExceptionStats() {
252             return mBinderCallsStats.getExportedExceptionStats();
253         }
254     }
255 
256     public static class LifeCycle extends SystemService {
257         private BinderCallsStatsService mService;
258         private BinderCallsStats mBinderCallsStats;
259         private AuthorizedWorkSourceProvider mWorkSourceProvider;
260 
LifeCycle(Context context)261         public LifeCycle(Context context) {
262             super(context);
263         }
264 
265         @Override
onStart()266         public void onStart() {
267             mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector());
268             mWorkSourceProvider = new AuthorizedWorkSourceProvider();
269             mService = new BinderCallsStatsService(
270                     mBinderCallsStats, mWorkSourceProvider);
271             publishLocalService(Internal.class, new Internal(mBinderCallsStats));
272             publishBinderService(SERVICE_NAME, mService);
273             boolean detailedTrackingEnabled = SystemProperties.getBoolean(
274                     PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, false);
275 
276             if (detailedTrackingEnabled) {
277                 Slog.i(TAG, "Enabled CPU usage tracking for binder calls. Controlled by "
278                         + PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING
279                         + " or via dumpsys binder_calls_stats --enable-detailed-tracking");
280                 mBinderCallsStats.setDetailedTracking(true);
281             }
282         }
283 
284         @Override
onBootPhase(int phase)285         public void onBootPhase(int phase) {
286             if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) {
287                 CachedDeviceState.Readonly deviceState = getLocalService(
288                         CachedDeviceState.Readonly.class);
289                 mBinderCallsStats.setDeviceState(deviceState);
290 
291                 BatteryStatsInternal batteryStatsInternal = getLocalService(
292                         BatteryStatsInternal.class);
293                 mBinderCallsStats.setCallStatsObserver(new BinderInternal.CallStatsObserver() {
294                     @Override
295                     public void noteCallStats(int workSourceUid, long incrementalCallCount,
296                             Collection<BinderCallsStats.CallStat> callStats) {
297                         batteryStatsInternal.noteBinderCallStats(workSourceUid,
298                                 incrementalCallCount, callStats);
299                     }
300 
301                     @Override
302                     public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) {
303                         batteryStatsInternal.noteBinderThreadNativeIds(binderThreadNativeTids);
304                     }
305                 });
306 
307                 // It needs to be called before mService.systemReady to make sure the observer is
308                 // initialized before installing it.
309                 mWorkSourceProvider.systemReady(getContext());
310                 mService.systemReady(getContext());
311             }
312         }
313     }
314 
315     private SettingsObserver mSettingsObserver;
316     private final BinderCallsStats mBinderCallsStats;
317     private final AuthorizedWorkSourceProvider mWorkSourceProvider;
318 
BinderCallsStatsService(BinderCallsStats binderCallsStats, AuthorizedWorkSourceProvider workSourceProvider)319     BinderCallsStatsService(BinderCallsStats binderCallsStats,
320             AuthorizedWorkSourceProvider workSourceProvider) {
321         mBinderCallsStats = binderCallsStats;
322         mWorkSourceProvider = workSourceProvider;
323     }
324 
systemReady(Context context)325     public void systemReady(Context context) {
326         mSettingsObserver = new SettingsObserver(context, mBinderCallsStats, mWorkSourceProvider);
327     }
328 
reset()329     public void reset() {
330         Slog.i(TAG, "Resetting stats");
331         mBinderCallsStats.reset();
332     }
333 
334     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)335     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
336         if (!DumpUtils.checkDumpAndUsageStatsPermission(ActivityThread.currentApplication(),
337                 SERVICE_NAME, pw)) {
338             return;
339         }
340 
341         boolean verbose = false;
342         int worksourceUid = Process.INVALID_UID;
343         if (args != null) {
344             for (int i = 0; i < args.length; i++) {
345                 String arg = args[i];
346                 if ("-a".equals(arg)) {
347                     verbose = true;
348                 } else if ("-h".equals(arg)) {
349                     pw.println("dumpsys binder_calls_stats options:");
350                     pw.println("  -a: Verbose");
351                     pw.println("  --work-source-uid <UID>: Dump binder calls from the UID");
352                     return;
353                 } else if ("--work-source-uid".equals(arg)) {
354                     i++;
355                     if (i >= args.length) {
356                         throw new IllegalArgumentException(
357                                 "Argument expected after \"" + arg + "\"");
358                     }
359                     String uidArg = args[i];
360                     try {
361                         worksourceUid = Integer.parseInt(uidArg);
362                     } catch (NumberFormatException e) {
363                         pw.println("Invalid UID: " + uidArg);
364                         return;
365                     }
366                 }
367             }
368 
369             if (args.length > 0 && worksourceUid == Process.INVALID_UID) {
370                 // For compatibility, support "cmd"-style commands when passed to "dumpsys".
371                 BinderCallsStatsShellCommand command = new BinderCallsStatsShellCommand(pw);
372                 int status = command.exec(this, null, FileDescriptor.out, FileDescriptor.err, args);
373                 if (status == 0) {
374                     return;
375                 }
376             }
377         }
378         mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), worksourceUid, verbose);
379     }
380 
381     @Override
handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, ParcelFileDescriptor err, String[] args)382     public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out,
383             ParcelFileDescriptor err, String[] args) {
384         ShellCommand command = new BinderCallsStatsShellCommand(null);
385         int status = command.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
386                 err.getFileDescriptor(), args);
387         if (status != 0) {
388             command.onHelp();
389         }
390         return status;
391     }
392 
393     private class BinderCallsStatsShellCommand extends ShellCommand {
394         private final PrintWriter mPrintWriter;
395 
BinderCallsStatsShellCommand(PrintWriter printWriter)396         BinderCallsStatsShellCommand(PrintWriter printWriter) {
397             mPrintWriter = printWriter;
398         }
399 
400         @Override
getOutPrintWriter()401         public PrintWriter getOutPrintWriter() {
402             if (mPrintWriter != null) {
403                 return mPrintWriter;
404             }
405             return super.getOutPrintWriter();
406         }
407 
408         @Override
onCommand(String cmd)409         public int onCommand(String cmd) {
410             PrintWriter pw = getOutPrintWriter();
411             if (cmd == null) {
412                 return -1;
413             }
414 
415             switch (cmd) {
416                 case "--reset":
417                     reset();
418                     pw.println("binder_calls_stats reset.");
419                     break;
420                 case "--enable":
421                     Binder.setObserver(mBinderCallsStats);
422                     break;
423                 case "--disable":
424                     Binder.setObserver(null);
425                     break;
426                 case "--no-sampling":
427                     mBinderCallsStats.setSamplingInterval(1);
428                     break;
429                 case "--enable-detailed-tracking":
430                     SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "1");
431                     mBinderCallsStats.setDetailedTracking(true);
432                     pw.println("Detailed tracking enabled");
433                     break;
434                 case "--disable-detailed-tracking":
435                     SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "");
436                     mBinderCallsStats.setDetailedTracking(false);
437                     pw.println("Detailed tracking disabled");
438                     break;
439                 case "--dump-worksource-provider":
440                     mBinderCallsStats.setDetailedTracking(true);
441                     mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot());
442                     break;
443                 case "--work-source-uid":
444                     String uidArg = getNextArgRequired();
445                     try {
446                         int uid = Integer.parseInt(uidArg);
447                         mBinderCallsStats.recordAllCallsForWorkSourceUid(uid);
448                     } catch (NumberFormatException e) {
449                         pw.println("Invalid UID: " + uidArg);
450                         return -1;
451                     }
452                     break;
453                 default:
454                     return handleDefaultCommands(cmd);
455             }
456             return 0;
457         }
458 
459         @Override
onHelp()460         public void onHelp() {
461             PrintWriter pw = getOutPrintWriter();
462             pw.println("binder_calls_stats commands:");
463             pw.println("  --reset: Reset stats");
464             pw.println("  --enable: Enable tracking binder calls");
465             pw.println("  --disable: Disables tracking binder calls");
466             pw.println("  --no-sampling: Tracks all calls");
467             pw.println("  --enable-detailed-tracking: Enables detailed tracking");
468             pw.println("  --disable-detailed-tracking: Disables detailed tracking");
469             pw.println("  --work-source-uid <UID>: Track all binder calls from the UID");
470         }
471     }
472 }
473