1 /* 2 * Copyright (C) 2019 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.stats; 18 19 import static com.android.server.stats.StatsCompanion.PendingIntentRef; 20 21 import android.Manifest; 22 import android.annotation.Nullable; 23 import android.app.AppOpsManager; 24 import android.app.PendingIntent; 25 import android.content.Context; 26 import android.os.Binder; 27 import android.os.IPullAtomCallback; 28 import android.os.IStatsManagerService; 29 import android.os.IStatsQueryCallback; 30 import android.os.IStatsd; 31 import android.os.ParcelFileDescriptor; 32 import android.os.PowerManager; 33 import android.os.Process; 34 import android.os.RemoteException; 35 import android.util.ArrayMap; 36 import android.util.Log; 37 38 import com.android.internal.annotations.GuardedBy; 39 40 import java.io.DataInputStream; 41 import java.io.DataOutputStream; 42 import java.io.FileDescriptor; 43 import java.io.FileInputStream; 44 import java.io.FileOutputStream; 45 import java.io.IOException; 46 import java.util.Map; 47 import java.util.Objects; 48 49 /** 50 * Service for {@link android.app.StatsManager}. 51 * 52 * @hide 53 */ 54 public class StatsManagerService extends IStatsManagerService.Stub { 55 56 private static final String TAG = "StatsManagerService"; 57 private static final boolean DEBUG = false; 58 59 private static final int STATSD_TIMEOUT_MILLIS = 5000; 60 61 private static final String USAGE_STATS_PERMISSION_OPS = "android:get_usage_stats"; 62 63 @GuardedBy("mLock") 64 private IStatsd mStatsd; 65 private final Object mLock = new Object(); 66 67 private StatsCompanionService mStatsCompanionService; 68 private Context mContext; 69 70 @GuardedBy("mLock") 71 private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>(); 72 @GuardedBy("mLock") 73 private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>(); 74 @GuardedBy("mLock") 75 private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap = 76 new ArrayMap<>(); 77 @GuardedBy("mLock") 78 private ArrayMap<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> 79 mRestrictedMetricsPirMap = new ArrayMap<>(); 80 StatsManagerService(Context context)81 public StatsManagerService(Context context) { 82 super(); 83 mContext = context; 84 } 85 86 private static class ConfigKey { 87 private final int mUid; 88 private final long mConfigId; 89 ConfigKey(int uid, long configId)90 ConfigKey(int uid, long configId) { 91 mUid = uid; 92 mConfigId = configId; 93 } 94 getUid()95 public int getUid() { 96 return mUid; 97 } 98 getConfigId()99 public long getConfigId() { 100 return mConfigId; 101 } 102 103 @Override hashCode()104 public int hashCode() { 105 return Objects.hash(mUid, mConfigId); 106 } 107 108 @Override equals(Object obj)109 public boolean equals(Object obj) { 110 if (obj instanceof ConfigKey) { 111 ConfigKey other = (ConfigKey) obj; 112 return this.mUid == other.getUid() && this.mConfigId == other.getConfigId(); 113 } 114 return false; 115 } 116 } 117 118 private static class ConfigKeyWithPackage { 119 private final String mConfigPackage; 120 private final long mConfigId; 121 ConfigKeyWithPackage(String configPackage, long configId)122 ConfigKeyWithPackage(String configPackage, long configId) { 123 mConfigPackage = configPackage; 124 mConfigId = configId; 125 } 126 getConfigPackage()127 public String getConfigPackage() { 128 return mConfigPackage; 129 } 130 getConfigId()131 public long getConfigId() { 132 return mConfigId; 133 } 134 135 @Override hashCode()136 public int hashCode() { 137 return Objects.hash(mConfigPackage, mConfigId); 138 } 139 140 @Override equals(Object obj)141 public boolean equals(Object obj) { 142 if (obj instanceof ConfigKeyWithPackage) { 143 ConfigKeyWithPackage other = (ConfigKeyWithPackage) obj; 144 return this.mConfigPackage.equals(other.getConfigPackage()) 145 && this.mConfigId == other.getConfigId(); 146 } 147 return false; 148 } 149 } 150 151 private static class PullerKey { 152 private final int mUid; 153 private final int mAtomTag; 154 PullerKey(int uid, int atom)155 PullerKey(int uid, int atom) { 156 mUid = uid; 157 mAtomTag = atom; 158 } 159 getUid()160 public int getUid() { 161 return mUid; 162 } 163 getAtom()164 public int getAtom() { 165 return mAtomTag; 166 } 167 168 @Override hashCode()169 public int hashCode() { 170 return Objects.hash(mUid, mAtomTag); 171 } 172 173 @Override equals(Object obj)174 public boolean equals(Object obj) { 175 if (obj instanceof PullerKey) { 176 PullerKey other = (PullerKey) obj; 177 return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); 178 } 179 return false; 180 } 181 } 182 183 private static class PullerValue { 184 private final long mCoolDownMillis; 185 private final long mTimeoutMillis; 186 private final int[] mAdditiveFields; 187 private final IPullAtomCallback mCallback; 188 PullerValue(long coolDownMillis, long timeoutMillis, int[] additiveFields, IPullAtomCallback callback)189 PullerValue(long coolDownMillis, long timeoutMillis, int[] additiveFields, 190 IPullAtomCallback callback) { 191 mCoolDownMillis = coolDownMillis; 192 mTimeoutMillis = timeoutMillis; 193 mAdditiveFields = additiveFields; 194 mCallback = callback; 195 } 196 getCoolDownMillis()197 public long getCoolDownMillis() { 198 return mCoolDownMillis; 199 } 200 getTimeoutMillis()201 public long getTimeoutMillis() { 202 return mTimeoutMillis; 203 } 204 getAdditiveFields()205 public int[] getAdditiveFields() { 206 return mAdditiveFields; 207 } 208 getCallback()209 public IPullAtomCallback getCallback() { 210 return mCallback; 211 } 212 } 213 214 private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>(); 215 216 @Override registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis, int[] additiveFields, IPullAtomCallback pullerCallback)217 public void registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis, 218 int[] additiveFields, IPullAtomCallback pullerCallback) { 219 enforceRegisterStatsPullAtomPermission(); 220 if (pullerCallback == null) { 221 Log.w(TAG, "Puller callback is null for atom " + atomTag); 222 return; 223 } 224 int callingUid = Binder.getCallingUid(); 225 PullerKey key = new PullerKey(callingUid, atomTag); 226 PullerValue val = 227 new PullerValue(coolDownMillis, timeoutMillis, additiveFields, pullerCallback); 228 229 // Always cache the puller in StatsManagerService. If statsd is down, we will register the 230 // puller when statsd comes back up. 231 synchronized (mLock) { 232 mPullers.put(key, val); 233 } 234 235 IStatsd statsd = getStatsdNonblocking(); 236 if (statsd == null) { 237 return; 238 } 239 240 final long token = Binder.clearCallingIdentity(); 241 try { 242 statsd.registerPullAtomCallback(callingUid, atomTag, coolDownMillis, timeoutMillis, 243 additiveFields, pullerCallback); 244 } catch (RemoteException e) { 245 Log.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); 246 } finally { 247 Binder.restoreCallingIdentity(token); 248 } 249 } 250 251 @Override unregisterPullAtomCallback(int atomTag)252 public void unregisterPullAtomCallback(int atomTag) { 253 enforceRegisterStatsPullAtomPermission(); 254 int callingUid = Binder.getCallingUid(); 255 PullerKey key = new PullerKey(callingUid, atomTag); 256 257 // Always remove the puller from StatsManagerService even if statsd is down. When statsd 258 // comes back up, we will not re-register the removed puller. 259 synchronized (mLock) { 260 mPullers.remove(key); 261 } 262 263 IStatsd statsd = getStatsdNonblocking(); 264 if (statsd == null) { 265 return; 266 } 267 268 final long token = Binder.clearCallingIdentity(); 269 try { 270 statsd.unregisterPullAtomCallback(callingUid, atomTag); 271 } catch (RemoteException e) { 272 Log.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); 273 } finally { 274 Binder.restoreCallingIdentity(token); 275 } 276 } 277 278 @Override setDataFetchOperation(long configId, PendingIntent pendingIntent, String packageName)279 public void setDataFetchOperation(long configId, PendingIntent pendingIntent, 280 String packageName) { 281 enforceDumpAndUsageStatsPermission(packageName); 282 int callingUid = Binder.getCallingUid(); 283 final long token = Binder.clearCallingIdentity(); 284 PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext); 285 ConfigKey key = new ConfigKey(callingUid, configId); 286 // We add the PIR to a map so we can reregister if statsd is unavailable. 287 synchronized (mLock) { 288 mDataFetchPirMap.put(key, pir); 289 } 290 try { 291 IStatsd statsd = getStatsdNonblocking(); 292 if (statsd != null) { 293 statsd.setDataFetchOperation(configId, pir, callingUid); 294 } 295 } catch (RemoteException e) { 296 Log.e(TAG, "Failed to setDataFetchOperation with statsd"); 297 } finally { 298 Binder.restoreCallingIdentity(token); 299 } 300 } 301 302 @Override removeDataFetchOperation(long configId, String packageName)303 public void removeDataFetchOperation(long configId, String packageName) { 304 enforceDumpAndUsageStatsPermission(packageName); 305 int callingUid = Binder.getCallingUid(); 306 final long token = Binder.clearCallingIdentity(); 307 ConfigKey key = new ConfigKey(callingUid, configId); 308 synchronized (mLock) { 309 mDataFetchPirMap.remove(key); 310 } 311 try { 312 IStatsd statsd = getStatsdNonblocking(); 313 if (statsd != null) { 314 statsd.removeDataFetchOperation(configId, callingUid); 315 } 316 } catch (RemoteException e) { 317 Log.e(TAG, "Failed to removeDataFetchOperation with statsd"); 318 } finally { 319 Binder.restoreCallingIdentity(token); 320 } 321 } 322 323 @Override setActiveConfigsChangedOperation(PendingIntent pendingIntent, String packageName)324 public long[] setActiveConfigsChangedOperation(PendingIntent pendingIntent, 325 String packageName) { 326 enforceDumpAndUsageStatsPermission(packageName); 327 int callingUid = Binder.getCallingUid(); 328 final long token = Binder.clearCallingIdentity(); 329 PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext); 330 // We add the PIR to a map so we can reregister if statsd is unavailable. 331 synchronized (mLock) { 332 mActiveConfigsPirMap.put(callingUid, pir); 333 } 334 try { 335 IStatsd statsd = getStatsdNonblocking(); 336 if (statsd != null) { 337 return statsd.setActiveConfigsChangedOperation(pir, callingUid); 338 } 339 } catch (RemoteException e) { 340 Log.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd"); 341 } finally { 342 Binder.restoreCallingIdentity(token); 343 } 344 return new long[] {}; 345 } 346 347 @Override removeActiveConfigsChangedOperation(String packageName)348 public void removeActiveConfigsChangedOperation(String packageName) { 349 enforceDumpAndUsageStatsPermission(packageName); 350 int callingUid = Binder.getCallingUid(); 351 final long token = Binder.clearCallingIdentity(); 352 synchronized (mLock) { 353 mActiveConfigsPirMap.remove(callingUid); 354 } 355 try { 356 IStatsd statsd = getStatsdNonblocking(); 357 if (statsd != null) { 358 statsd.removeActiveConfigsChangedOperation(callingUid); 359 } 360 } catch (RemoteException e) { 361 Log.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd"); 362 } finally { 363 Binder.restoreCallingIdentity(token); 364 } 365 } 366 367 @Override setBroadcastSubscriber(long configId, long subscriberId, PendingIntent pendingIntent, String packageName)368 public void setBroadcastSubscriber(long configId, long subscriberId, 369 PendingIntent pendingIntent, String packageName) { 370 enforceDumpAndUsageStatsPermission(packageName); 371 int callingUid = Binder.getCallingUid(); 372 final long token = Binder.clearCallingIdentity(); 373 PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext); 374 ConfigKey key = new ConfigKey(callingUid, configId); 375 // We add the PIR to a map so we can reregister if statsd is unavailable. 376 synchronized (mLock) { 377 ArrayMap<Long, PendingIntentRef> innerMap = mBroadcastSubscriberPirMap 378 .getOrDefault(key, new ArrayMap<>()); 379 innerMap.put(subscriberId, pir); 380 mBroadcastSubscriberPirMap.put(key, innerMap); 381 } 382 try { 383 IStatsd statsd = getStatsdNonblocking(); 384 if (statsd != null) { 385 statsd.setBroadcastSubscriber( 386 configId, subscriberId, pir, callingUid); 387 } 388 } catch (RemoteException e) { 389 Log.e(TAG, "Failed to setBroadcastSubscriber with statsd"); 390 } finally { 391 Binder.restoreCallingIdentity(token); 392 } 393 } 394 395 @Override unsetBroadcastSubscriber(long configId, long subscriberId, String packageName)396 public void unsetBroadcastSubscriber(long configId, long subscriberId, String packageName) { 397 enforceDumpAndUsageStatsPermission(packageName); 398 int callingUid = Binder.getCallingUid(); 399 final long token = Binder.clearCallingIdentity(); 400 ConfigKey key = new ConfigKey(callingUid, configId); 401 synchronized (mLock) { 402 ArrayMap<Long, PendingIntentRef> innerMap = mBroadcastSubscriberPirMap 403 .getOrDefault(key, new ArrayMap<>()); 404 innerMap.remove(subscriberId); 405 if (innerMap.isEmpty()) { 406 mBroadcastSubscriberPirMap.remove(key); 407 } 408 } 409 try { 410 IStatsd statsd = getStatsdNonblocking(); 411 if (statsd != null) { 412 statsd.unsetBroadcastSubscriber(configId, subscriberId, callingUid); 413 } 414 } catch (RemoteException e) { 415 Log.e(TAG, "Failed to unsetBroadcastSubscriber with statsd"); 416 } finally { 417 Binder.restoreCallingIdentity(token); 418 } 419 } 420 421 @Override getRegisteredExperimentIds()422 public long[] getRegisteredExperimentIds() throws IllegalStateException { 423 enforceDumpAndUsageStatsPermission(null); 424 final long token = Binder.clearCallingIdentity(); 425 try { 426 IStatsd statsd = waitForStatsd(); 427 if (statsd != null) { 428 return statsd.getRegisteredExperimentIds(); 429 } 430 } catch (RemoteException e) { 431 Log.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); 432 throw new IllegalStateException(e.getMessage(), e); 433 } finally { 434 Binder.restoreCallingIdentity(token); 435 } 436 throw new IllegalStateException("Failed to connect to statsd to registerExperimentIds"); 437 } 438 439 @Override getMetadata(String packageName)440 public byte[] getMetadata(String packageName) throws IllegalStateException { 441 enforceDumpAndUsageStatsPermission(packageName); 442 final long token = Binder.clearCallingIdentity(); 443 try { 444 IStatsd statsd = waitForStatsd(); 445 if (statsd != null) { 446 return statsd.getMetadata(); 447 } 448 } catch (RemoteException e) { 449 Log.e(TAG, "Failed to getMetadata with statsd"); 450 throw new IllegalStateException(e.getMessage(), e); 451 } finally { 452 Binder.restoreCallingIdentity(token); 453 } 454 throw new IllegalStateException("Failed to connect to statsd to getMetadata"); 455 } 456 457 @Override getData(long key, String packageName)458 public byte[] getData(long key, String packageName) throws IllegalStateException { 459 enforceDumpAndUsageStatsPermission(packageName); 460 PowerManager powerManager = mContext.getSystemService(PowerManager.class); 461 PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 462 /*tag=*/ StatsManagerService.class.getCanonicalName()); 463 final int callingUid = Binder.getCallingUid(); 464 final long token = Binder.clearCallingIdentity(); 465 wl.acquire(); 466 try { 467 IStatsd statsd = waitForStatsd(); 468 if (statsd != null) { 469 return statsd.getData(key, callingUid); 470 } 471 } catch (RemoteException e) { 472 Log.e(TAG, "Failed to getData with statsd"); 473 throw new IllegalStateException(e.getMessage(), e); 474 } finally { 475 wl.release(); 476 Binder.restoreCallingIdentity(token); 477 } 478 throw new IllegalStateException("Failed to connect to statsd to getData"); 479 } 480 481 @Override getDataFd(long key, String packageName, ParcelFileDescriptor writeFd)482 public void getDataFd(long key, String packageName, ParcelFileDescriptor writeFd) 483 throws IllegalStateException, RemoteException { 484 try (writeFd) { 485 enforceDumpAndUsageStatsPermission(packageName); 486 PowerManager powerManager = mContext.getSystemService(PowerManager.class); 487 PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 488 /*tag=*/ StatsManagerService.class.getCanonicalName()); 489 final int callingUid = Binder.getCallingUid(); 490 final long token = Binder.clearCallingIdentity(); 491 wl.acquire(); 492 try { 493 IStatsd statsd = waitForStatsd(); 494 if (statsd != null) { 495 // will create another intermediate pipe for statsd 496 getDataFdFromStatsd(statsd, key, callingUid, writeFd.getFileDescriptor()); 497 return; 498 } 499 } finally { 500 wl.release(); 501 Binder.restoreCallingIdentity(token); 502 } 503 } catch (IOException e) { 504 throw new IllegalStateException("IOException during getDataFd() call", e); 505 } 506 throw new IllegalStateException("Failed to connect to statsd to getDataFd"); 507 } 508 509 @Override addConfiguration(long configId, byte[] config, String packageName)510 public void addConfiguration(long configId, byte[] config, String packageName) 511 throws IllegalStateException { 512 enforceDumpAndUsageStatsPermission(packageName); 513 int callingUid = Binder.getCallingUid(); 514 final long token = Binder.clearCallingIdentity(); 515 try { 516 IStatsd statsd = waitForStatsd(); 517 if (statsd != null) { 518 statsd.addConfiguration(configId, config, callingUid); 519 return; 520 } 521 } catch (RemoteException e) { 522 Log.e(TAG, "Failed to addConfiguration with statsd"); 523 throw new IllegalStateException(e.getMessage(), e); 524 } finally { 525 Binder.restoreCallingIdentity(token); 526 } 527 throw new IllegalStateException("Failed to connect to statsd to addConfig"); 528 } 529 530 @Override removeConfiguration(long configId, String packageName)531 public void removeConfiguration(long configId, String packageName) 532 throws IllegalStateException { 533 enforceDumpAndUsageStatsPermission(packageName); 534 int callingUid = Binder.getCallingUid(); 535 final long token = Binder.clearCallingIdentity(); 536 try { 537 IStatsd statsd = waitForStatsd(); 538 if (statsd != null) { 539 statsd.removeConfiguration(configId, callingUid); 540 return; 541 } 542 } catch (RemoteException e) { 543 Log.e(TAG, "Failed to removeConfiguration with statsd"); 544 throw new IllegalStateException(e.getMessage(), e); 545 } finally { 546 Binder.restoreCallingIdentity(token); 547 } 548 throw new IllegalStateException("Failed to connect to statsd to removeConfig"); 549 } 550 551 @Override setRestrictedMetricsChangedOperation(PendingIntent pendingIntent, long configId, String configPackage)552 public long[] setRestrictedMetricsChangedOperation(PendingIntent pendingIntent, 553 long configId, String configPackage) { 554 enforceRestrictedStatsPermission(); 555 int callingUid = Binder.getCallingUid(); 556 final long token = Binder.clearCallingIdentity(); 557 PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext); 558 ConfigKeyWithPackage key = new ConfigKeyWithPackage(configPackage, configId); 559 // Add the PIR to a map so we can re-register if statsd is unavailable. 560 synchronized (mLock) { 561 ArrayMap<Integer, PendingIntentRef> innerMap = mRestrictedMetricsPirMap.getOrDefault( 562 key, new ArrayMap<>()); 563 innerMap.put(callingUid, pir); 564 mRestrictedMetricsPirMap.put(key, innerMap); 565 } 566 try { 567 IStatsd statsd = getStatsdNonblocking(); 568 if (statsd != null) { 569 return statsd.setRestrictedMetricsChangedOperation(configId, configPackage, pir, 570 callingUid); 571 } 572 } catch (RemoteException e) { 573 Log.e(TAG, "Failed to setRestrictedMetricsChangedOperation with statsd"); 574 } finally { 575 Binder.restoreCallingIdentity(token); 576 } 577 return new long[]{}; 578 } 579 580 @Override removeRestrictedMetricsChangedOperation(long configId, String configPackage)581 public void removeRestrictedMetricsChangedOperation(long configId, String configPackage) { 582 enforceRestrictedStatsPermission(); 583 int callingUid = Binder.getCallingUid(); 584 final long token = Binder.clearCallingIdentity(); 585 ConfigKeyWithPackage key = new ConfigKeyWithPackage(configPackage, configId); 586 synchronized (mLock) { 587 ArrayMap<Integer, PendingIntentRef> innerMap = mRestrictedMetricsPirMap.getOrDefault( 588 key, new ArrayMap<>()); 589 innerMap.remove(callingUid); 590 if (innerMap.isEmpty()) { 591 mRestrictedMetricsPirMap.remove(key); 592 } 593 } 594 try { 595 IStatsd statsd = getStatsdNonblocking(); 596 if (statsd != null) { 597 statsd.removeRestrictedMetricsChangedOperation(configId, configPackage, callingUid); 598 } 599 } catch (RemoteException e) { 600 Log.e(TAG, "Failed to removeRestrictedMetricsChangedOperation with statsd"); 601 } finally { 602 Binder.restoreCallingIdentity(token); 603 } 604 } 605 606 @Override querySql(String sqlQuery, int minSqlClientVersion, byte[] policyConfig, IStatsQueryCallback queryCallback, long configKey, String configPackage)607 public void querySql(String sqlQuery, int minSqlClientVersion, byte[] policyConfig, 608 IStatsQueryCallback queryCallback, long configKey, String configPackage) { 609 int callingUid = Binder.getCallingUid(); 610 enforceRestrictedStatsPermission(); 611 final long token = Binder.clearCallingIdentity(); 612 try { 613 IStatsd statsd = waitForStatsd(); 614 if (statsd != null) { 615 statsd.querySql( 616 sqlQuery, 617 minSqlClientVersion, 618 policyConfig, 619 queryCallback, 620 configKey, 621 configPackage, 622 callingUid); 623 } else { 624 queryCallback.sendFailure("Could not connect to statsd from system server"); 625 } 626 } catch (RemoteException e) { 627 throw new IllegalStateException(e.getMessage(), e); 628 } finally { 629 Binder.restoreCallingIdentity(token); 630 } 631 } 632 setStatsCompanionService(StatsCompanionService statsCompanionService)633 void setStatsCompanionService(StatsCompanionService statsCompanionService) { 634 mStatsCompanionService = statsCompanionService; 635 } 636 637 /** Checks that the caller has READ_RESTRICTED_STATS permission. */ enforceRestrictedStatsPermission()638 private void enforceRestrictedStatsPermission() { 639 mContext.enforceCallingPermission(Manifest.permission.READ_RESTRICTED_STATS, null); 640 } 641 642 /** 643 * Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that 644 * the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null. 645 * 646 * @param packageName The packageName to check USAGE_STATS_PERMISSION_OPS. 647 */ enforceDumpAndUsageStatsPermission(@ullable String packageName)648 private void enforceDumpAndUsageStatsPermission(@Nullable String packageName) { 649 int callingUid = Binder.getCallingUid(); 650 int callingPid = Binder.getCallingPid(); 651 652 if (callingPid == Process.myPid()) { 653 return; 654 } 655 656 mContext.enforceCallingPermission(Manifest.permission.DUMP, null); 657 mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, null); 658 659 if (packageName == null) { 660 return; 661 } 662 AppOpsManager appOpsManager = (AppOpsManager) mContext 663 .getSystemService(Context.APP_OPS_SERVICE); 664 switch (appOpsManager.noteOp(USAGE_STATS_PERMISSION_OPS, 665 Binder.getCallingUid(), packageName, null, null)) { 666 case AppOpsManager.MODE_ALLOWED: 667 case AppOpsManager.MODE_DEFAULT: 668 break; 669 default: 670 throw new SecurityException( 671 String.format("UID %d / PID %d lacks app-op %s", 672 callingUid, callingPid, USAGE_STATS_PERMISSION_OPS) 673 ); 674 } 675 } 676 enforceRegisterStatsPullAtomPermission()677 private void enforceRegisterStatsPullAtomPermission() { 678 mContext.enforceCallingOrSelfPermission( 679 android.Manifest.permission.REGISTER_STATS_PULL_ATOM, 680 "Need REGISTER_STATS_PULL_ATOM permission."); 681 } 682 683 684 /** 685 * Clients should call this if blocking until statsd to be ready is desired 686 * 687 * @return IStatsd object if statsd becomes ready within the timeout, null otherwise. 688 */ waitForStatsd()689 private IStatsd waitForStatsd() { 690 synchronized (mLock) { 691 if (mStatsd == null) { 692 try { 693 mLock.wait(STATSD_TIMEOUT_MILLIS); 694 } catch (InterruptedException e) { 695 Log.e(TAG, "wait for statsd interrupted"); 696 } 697 } 698 return mStatsd; 699 } 700 } 701 702 /** 703 * Clients should call this to receive a reference to statsd. 704 * 705 * @return IStatsd object if statsd is ready, null otherwise. 706 */ getStatsdNonblocking()707 private IStatsd getStatsdNonblocking() { 708 synchronized (mLock) { 709 return mStatsd; 710 } 711 } 712 713 /** 714 * Called from {@link StatsCompanionService}. 715 * 716 * Tells StatsManagerService that Statsd is ready and updates 717 * Statsd with the contents of our local cache. 718 */ statsdReady(IStatsd statsd)719 void statsdReady(IStatsd statsd) { 720 synchronized (mLock) { 721 mStatsd = statsd; 722 mLock.notify(); 723 } 724 sayHiToStatsd(statsd); 725 } 726 727 /** 728 * Called from {@link StatsCompanionService}. 729 * 730 * Tells StatsManagerService that Statsd is no longer ready 731 * and we should no longer make binder calls with statsd. 732 */ statsdNotReady()733 void statsdNotReady() { 734 synchronized (mLock) { 735 mStatsd = null; 736 } 737 } 738 sayHiToStatsd(IStatsd statsd)739 private void sayHiToStatsd(IStatsd statsd) { 740 if (statsd == null) { 741 return; 742 } 743 744 final long token = Binder.clearCallingIdentity(); 745 try { 746 registerAllPullers(statsd); 747 registerAllDataFetchOperations(statsd); 748 registerAllActiveConfigsChangedOperations(statsd); 749 registerAllBroadcastSubscribers(statsd); 750 registerAllRestrictedMetricsChangedOperations(statsd); 751 // TODO (b/269419485): register all restricted metric operations. 752 } catch (RemoteException e) { 753 Log.e(TAG, "StatsManager failed to (re-)register data with statsd"); 754 } finally { 755 Binder.restoreCallingIdentity(token); 756 } 757 } 758 759 // Pre-condition: the Binder calling identity has already been cleared registerAllPullers(IStatsd statsd)760 private void registerAllPullers(IStatsd statsd) throws RemoteException { 761 // Since we do not want to make an IPC with the lock held, we first create a copy of the 762 // data with the lock held before iterating through the map. 763 ArrayMap<PullerKey, PullerValue> pullersCopy; 764 synchronized (mLock) { 765 pullersCopy = new ArrayMap<>(mPullers); 766 } 767 768 for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) { 769 PullerKey key = entry.getKey(); 770 PullerValue value = entry.getValue(); 771 statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownMillis(), 772 value.getTimeoutMillis(), value.getAdditiveFields(), value.getCallback()); 773 } 774 statsd.allPullersFromBootRegistered(); 775 } 776 777 // Pre-condition: the Binder calling identity has already been cleared registerAllDataFetchOperations(IStatsd statsd)778 private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException { 779 // Since we do not want to make an IPC with the lock held, we first create a copy of the 780 // data with the lock held before iterating through the map. 781 ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy; 782 synchronized (mLock) { 783 dataFetchCopy = new ArrayMap<>(mDataFetchPirMap); 784 } 785 786 for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) { 787 ConfigKey key = entry.getKey(); 788 statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid()); 789 } 790 } 791 792 // Pre-condition: the Binder calling identity has already been cleared registerAllActiveConfigsChangedOperations(IStatsd statsd)793 private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException { 794 // Since we do not want to make an IPC with the lock held, we first create a copy of the 795 // data with the lock held before iterating through the map. 796 ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy; 797 synchronized (mLock) { 798 activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap); 799 } 800 801 for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) { 802 statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey()); 803 } 804 } 805 806 // Pre-condition: the Binder calling identity has already been cleared registerAllBroadcastSubscribers(IStatsd statsd)807 private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException { 808 // Since we do not want to make an IPC with the lock held, we first create a deep copy of 809 // the data with the lock held before iterating through the map. 810 ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy = 811 new ArrayMap<>(); 812 synchronized (mLock) { 813 for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : 814 mBroadcastSubscriberPirMap.entrySet()) { 815 broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue())); 816 } 817 } 818 819 for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : 820 broadcastSubscriberCopy.entrySet()) { 821 ConfigKey configKey = entry.getKey(); 822 for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) { 823 statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(), 824 subscriberEntry.getValue(), configKey.getUid()); 825 } 826 } 827 } 828 829 // Pre-condition: the Binder calling identity has already been cleared registerAllRestrictedMetricsChangedOperations(IStatsd statsd)830 private void registerAllRestrictedMetricsChangedOperations(IStatsd statsd) 831 throws RemoteException { 832 // Since we do not want to make an IPC with the lock held, we first create a deep copy of 833 // the data with the lock held before iterating through the map. 834 ArrayMap<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> restrictedMetricsCopy = 835 new ArrayMap<>(); 836 synchronized (mLock) { 837 for (Map.Entry<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> entry : 838 mRestrictedMetricsPirMap.entrySet()) { 839 restrictedMetricsCopy.put(entry.getKey(), new ArrayMap(entry.getValue())); 840 } 841 } 842 843 for (Map.Entry<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> entry : 844 restrictedMetricsCopy.entrySet()) { 845 ConfigKeyWithPackage configKey = entry.getKey(); 846 for (Map.Entry<Integer, PendingIntentRef> uidEntry : entry.getValue().entrySet()) { 847 statsd.setRestrictedMetricsChangedOperation(configKey.getConfigId(), 848 configKey.getConfigPackage(), uidEntry.getValue(), uidEntry.getKey()); 849 } 850 } 851 } 852 private static final int CHUNK_SIZE = 1024 * 64; // 64 kB 853 854 /** 855 * Executes a binder transaction with file descriptors. 856 * 857 * No exception handling in this API since they will not be propagated back to caller 858 * to make debugging easier, since this API part of oneway binder call flow. 859 */ getDataFdFromStatsd(IStatsd service, long configKey, int callingUid, FileDescriptor dstFd)860 private static void getDataFdFromStatsd(IStatsd service, long configKey, int callingUid, 861 FileDescriptor dstFd) 862 throws IllegalStateException, RemoteException { 863 ParcelFileDescriptor[] pipe; 864 try { 865 pipe = ParcelFileDescriptor.createPipe(); 866 } catch (IOException e) { 867 Log.e(TAG, "Failed to create a pipe to receive reports.", e); 868 throw new IllegalStateException("Failed to create a pipe to receive reports.", e); 869 } 870 871 ParcelFileDescriptor readFd = pipe[0]; 872 ParcelFileDescriptor writeFd = pipe[1]; 873 874 // statsd write/flush will block until read() will start to consume data. 875 // OTOH read cannot start until binder sync operation is over. 876 // To decouple this dependency call to statsd should be async 877 service.getDataFd(configKey, callingUid, writeFd); 878 try { 879 writeFd.close(); 880 } catch (IOException e) { 881 Log.e(TAG, "Failed to close FD", e); 882 throw new IllegalStateException("Failed to close FD.", e); 883 } 884 885 // There are many possible exceptions below, to not forget close pipe descriptors 886 // wrapping in the try-with-resources statement 887 try (FileInputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readFd); 888 DataInputStream srcDataStream = new DataInputStream(inputStream); 889 FileOutputStream outStream = new FileOutputStream(dstFd); 890 DataOutputStream dstDataStream = new DataOutputStream(outStream)) { 891 892 byte[] chunk = new byte[CHUNK_SIZE]; 893 int chunkLen = 0; 894 int readBytes = 0; 895 // the data protocol is [int ExpectedReportSize][ReportBytes] 896 // where int denotes the following bytes count 897 final int expectedReportSize = srcDataStream.readInt(); 898 // communicate the report size 899 dstDataStream.writeInt(expectedReportSize); 900 // -1 denotes EOF 901 while ((chunkLen = inputStream.read(chunk, 0, CHUNK_SIZE)) != -1) { 902 dstDataStream.write(chunk, 0, chunkLen); 903 readBytes += chunkLen; 904 } 905 if (readBytes != expectedReportSize) { 906 throw new IllegalStateException("Incomplete data read from StatsD."); 907 } 908 } catch (IOException e) { 909 Log.e(TAG, "Failed to read data from statsd pipe", e); 910 throw new IllegalStateException("Failed to read data from statsd pipe.", e); 911 } 912 } 913 } 914