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 android.content.Context; 23 import android.content.pm.PackageInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.database.ContentObserver; 27 import android.net.Uri; 28 import android.os.Binder; 29 import android.os.Process; 30 import android.os.SystemProperties; 31 import android.os.UserHandle; 32 import android.provider.Settings; 33 import android.util.ArrayMap; 34 import android.util.ArraySet; 35 import android.util.KeyValueListParser; 36 import android.util.Slog; 37 38 import com.android.internal.os.AppIdToPackageMap; 39 import com.android.internal.os.BackgroundThread; 40 import com.android.internal.os.BinderCallsStats; 41 import com.android.internal.os.BinderInternal; 42 import com.android.internal.os.CachedDeviceState; 43 44 import java.io.FileDescriptor; 45 import java.io.PrintWriter; 46 import java.util.ArrayList; 47 import java.util.List; 48 49 public class BinderCallsStatsService extends Binder { 50 51 private static final String TAG = "BinderCallsStatsService"; 52 53 private static final String PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING 54 = "persist.sys.binder_calls_detailed_tracking"; 55 56 /** Resolves the work source of an incoming binder transaction. */ 57 static class AuthorizedWorkSourceProvider implements BinderInternal.WorkSourceProvider { 58 private ArraySet<Integer> mAppIdWhitelist; 59 60 AuthorizedWorkSourceProvider() { 61 mAppIdWhitelist = new ArraySet<>(); 62 } 63 64 public int resolveWorkSourceUid(int untrustedWorkSourceUid) { 65 final int callingUid = getCallingUid(); 66 final int appId = UserHandle.getAppId(callingUid); 67 if (mAppIdWhitelist.contains(appId)) { 68 final int workSource = untrustedWorkSourceUid; 69 final boolean isWorkSourceSet = workSource != Binder.UNSET_WORKSOURCE; 70 return isWorkSourceSet ? workSource : callingUid; 71 } 72 return callingUid; 73 } 74 75 public void systemReady(Context context) { 76 mAppIdWhitelist = createAppidWhitelist(context); 77 } 78 79 public void dump(PrintWriter pw, AppIdToPackageMap packageMap) { 80 pw.println("AppIds of apps that can set the work source:"); 81 final ArraySet<Integer> whitelist = mAppIdWhitelist; 82 for (Integer appId : whitelist) { 83 pw.println("\t- " + packageMap.mapAppId(appId)); 84 } 85 } 86 87 protected int getCallingUid() { 88 return Binder.getCallingUid(); 89 } 90 91 private ArraySet<Integer> createAppidWhitelist(Context context) { 92 // Use a local copy instead of mAppIdWhitelist to prevent concurrent read access. 93 final ArraySet<Integer> whitelist = new ArraySet<>(); 94 95 // We trust our own process. 96 whitelist.add(UserHandle.getAppId(Process.myUid())); 97 // We only need to initialize it once. UPDATE_DEVICE_STATS is a system permission. 98 final PackageManager pm = context.getPackageManager(); 99 final String[] permissions = { android.Manifest.permission.UPDATE_DEVICE_STATS }; 100 final int queryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; 101 final List<PackageInfo> packages = 102 pm.getPackagesHoldingPermissions(permissions, queryFlags); 103 final int packagesSize = packages.size(); 104 for (int i = 0; i < packagesSize; i++) { 105 final PackageInfo pkgInfo = packages.get(i); 106 try { 107 final int uid = pm.getPackageUid(pkgInfo.packageName, queryFlags); 108 final int appId = UserHandle.getAppId(uid); 109 whitelist.add(appId); 110 } catch (NameNotFoundException e) { 111 Slog.e(TAG, "Cannot find uid for package name " + pkgInfo.packageName, e); 112 } 113 } 114 return whitelist; 115 } 116 } 117 118 /** Listens for flag changes. */ 119 private static class SettingsObserver extends ContentObserver { 120 private static final String SETTINGS_ENABLED_KEY = "enabled"; 121 private static final String SETTINGS_DETAILED_TRACKING_KEY = "detailed_tracking"; 122 private static final String SETTINGS_UPLOAD_DATA_KEY = "upload_data"; 123 private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; 124 private static final String SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY = "track_screen_state"; 125 private static final String SETTINGS_TRACK_DIRECT_CALLING_UID_KEY = "track_calling_uid"; 126 private static final String SETTINGS_MAX_CALL_STATS_KEY = "max_call_stats_count"; 127 128 private boolean mEnabled; 129 private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS); 130 private final Context mContext; 131 private final KeyValueListParser mParser = new KeyValueListParser(','); 132 private final BinderCallsStats mBinderCallsStats; 133 private final AuthorizedWorkSourceProvider mWorkSourceProvider; 134 135 SettingsObserver(Context context, BinderCallsStats binderCallsStats, 136 AuthorizedWorkSourceProvider workSourceProvider) { 137 super(BackgroundThread.getHandler()); 138 mContext = context; 139 context.getContentResolver().registerContentObserver(mUri, false, this, 140 UserHandle.USER_SYSTEM); 141 mBinderCallsStats = binderCallsStats; 142 mWorkSourceProvider = workSourceProvider; 143 // Always kick once to ensure that we match current state 144 onChange(); 145 } 146 147 @Override 148 public void onChange(boolean selfChange, Uri uri, int userId) { 149 if (mUri.equals(uri)) { 150 onChange(); 151 } 152 } 153 154 public void onChange() { 155 // Do not overwrite the default set manually. 156 if (!SystemProperties.get(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING).isEmpty()) { 157 return; 158 } 159 160 try { 161 mParser.setString(Settings.Global.getString(mContext.getContentResolver(), 162 Settings.Global.BINDER_CALLS_STATS)); 163 } catch (IllegalArgumentException e) { 164 Slog.e(TAG, "Bad binder call stats settings", e); 165 } 166 mBinderCallsStats.setDetailedTracking(mParser.getBoolean( 167 SETTINGS_DETAILED_TRACKING_KEY, BinderCallsStats.DETAILED_TRACKING_DEFAULT)); 168 mBinderCallsStats.setSamplingInterval(mParser.getInt( 169 SETTINGS_SAMPLING_INTERVAL_KEY, 170 BinderCallsStats.PERIODIC_SAMPLING_INTERVAL_DEFAULT)); 171 mBinderCallsStats.setMaxBinderCallStats(mParser.getInt( 172 SETTINGS_MAX_CALL_STATS_KEY, 173 BinderCallsStats.MAX_BINDER_CALL_STATS_COUNT_DEFAULT)); 174 mBinderCallsStats.setTrackScreenInteractive( 175 mParser.getBoolean(SETTINGS_TRACK_SCREEN_INTERACTIVE_KEY, 176 BinderCallsStats.DEFAULT_TRACK_SCREEN_INTERACTIVE)); 177 mBinderCallsStats.setTrackDirectCallerUid( 178 mParser.getBoolean(SETTINGS_TRACK_DIRECT_CALLING_UID_KEY, 179 BinderCallsStats.DEFAULT_TRACK_DIRECT_CALLING_UID)); 180 181 182 final boolean enabled = 183 mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT); 184 if (mEnabled != enabled) { 185 if (enabled) { 186 Binder.setObserver(mBinderCallsStats); 187 Binder.setProxyTransactListener( 188 new Binder.PropagateWorkSourceTransactListener()); 189 Binder.setWorkSourceProvider(mWorkSourceProvider); 190 } else { 191 Binder.setObserver(null); 192 Binder.setProxyTransactListener(null); 193 Binder.setWorkSourceProvider((x) -> Binder.getCallingUid()); 194 } 195 mEnabled = enabled; 196 mBinderCallsStats.reset(); 197 mBinderCallsStats.setAddDebugEntries(enabled); 198 } 199 } 200 } 201 202 /** 203 * @hide Only for use within the system server. 204 */ 205 public static class Internal { 206 private final BinderCallsStats mBinderCallsStats; 207 208 Internal(BinderCallsStats binderCallsStats) { 209 this.mBinderCallsStats = binderCallsStats; 210 } 211 212 /** @see BinderCallsStats#reset */ 213 public void reset() { 214 mBinderCallsStats.reset(); 215 } 216 217 /** 218 * @see BinderCallsStats#getExportedCallStats. 219 * 220 * Note that binder calls stats will be reset by statsd every time 221 * the data is exported. 222 */ 223 public ArrayList<BinderCallsStats.ExportedCallStat> getExportedCallStats() { 224 return mBinderCallsStats.getExportedCallStats(); 225 } 226 227 /** @see BinderCallsStats#getExportedExceptionStats */ 228 public ArrayMap<String, Integer> getExportedExceptionStats() { 229 return mBinderCallsStats.getExportedExceptionStats(); 230 } 231 } 232 233 public static class LifeCycle extends SystemService { 234 private BinderCallsStatsService mService; 235 private BinderCallsStats mBinderCallsStats; 236 private AuthorizedWorkSourceProvider mWorkSourceProvider; 237 238 public LifeCycle(Context context) { 239 super(context); 240 } 241 242 @Override 243 public void onStart() { 244 mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector()); 245 mWorkSourceProvider = new AuthorizedWorkSourceProvider(); 246 mService = new BinderCallsStatsService( 247 mBinderCallsStats, mWorkSourceProvider); 248 publishLocalService(Internal.class, new Internal(mBinderCallsStats)); 249 publishBinderService("binder_calls_stats", mService); 250 boolean detailedTrackingEnabled = SystemProperties.getBoolean( 251 PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, false); 252 253 if (detailedTrackingEnabled) { 254 Slog.i(TAG, "Enabled CPU usage tracking for binder calls. Controlled by " 255 + PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING 256 + " or via dumpsys binder_calls_stats --enable-detailed-tracking"); 257 mBinderCallsStats.setDetailedTracking(true); 258 } 259 } 260 261 @Override 262 public void onBootPhase(int phase) { 263 if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) { 264 CachedDeviceState.Readonly deviceState = getLocalService( 265 CachedDeviceState.Readonly.class); 266 mBinderCallsStats.setDeviceState(deviceState); 267 // It needs to be called before mService.systemReady to make sure the observer is 268 // initialized before installing it. 269 mWorkSourceProvider.systemReady(getContext()); 270 mService.systemReady(getContext()); 271 } 272 } 273 } 274 275 private SettingsObserver mSettingsObserver; 276 private final BinderCallsStats mBinderCallsStats; 277 private final AuthorizedWorkSourceProvider mWorkSourceProvider; 278 279 BinderCallsStatsService(BinderCallsStats binderCallsStats, 280 AuthorizedWorkSourceProvider workSourceProvider) { 281 mBinderCallsStats = binderCallsStats; 282 mWorkSourceProvider = workSourceProvider; 283 } 284 285 public void systemReady(Context context) { 286 mSettingsObserver = new SettingsObserver(context, mBinderCallsStats, mWorkSourceProvider); 287 } 288 289 public void reset() { 290 Slog.i(TAG, "Resetting stats"); 291 mBinderCallsStats.reset(); 292 } 293 294 @Override 295 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 296 boolean verbose = false; 297 if (args != null) { 298 for (final String arg : args) { 299 if ("-a".equals(arg)) { 300 verbose = true; 301 } else if ("--reset".equals(arg)) { 302 reset(); 303 pw.println("binder_calls_stats reset."); 304 return; 305 } else if ("--enable".equals(arg)) { 306 Binder.setObserver(mBinderCallsStats); 307 return; 308 } else if ("--disable".equals(arg)) { 309 Binder.setObserver(null); 310 return; 311 } else if ("--no-sampling".equals(arg)) { 312 mBinderCallsStats.setSamplingInterval(1); 313 return; 314 } else if ("--enable-detailed-tracking".equals(arg)) { 315 SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, "1"); 316 mBinderCallsStats.setDetailedTracking(true); 317 pw.println("Detailed tracking enabled"); 318 return; 319 } else if ("--disable-detailed-tracking".equals(arg)) { 320 SystemProperties.set(PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING, ""); 321 mBinderCallsStats.setDetailedTracking(false); 322 pw.println("Detailed tracking disabled"); 323 return; 324 } else if ("--dump-worksource-provider".equals(arg)) { 325 mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot()); 326 return; 327 } else if ("-h".equals(arg)) { 328 pw.println("binder_calls_stats commands:"); 329 pw.println(" --reset: Reset stats"); 330 pw.println(" --enable: Enable tracking binder calls"); 331 pw.println(" --disable: Disables tracking binder calls"); 332 pw.println(" --no-sampling: Tracks all calls"); 333 pw.println(" --enable-detailed-tracking: Enables detailed tracking"); 334 pw.println(" --disable-detailed-tracking: Disables detailed tracking"); 335 return; 336 } else { 337 pw.println("Unknown option: " + arg); 338 } 339 } 340 } 341 mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), verbose); 342 } 343 } 344