1 /* 2 * Copyright (C) 2023 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.connectivity; 18 19 import static android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; 20 21 import android.annotation.NonNull; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.net.Network; 27 import android.net.NetworkCapabilities; 28 import android.net.NetworkSpecifier; 29 import android.net.TelephonyNetworkSpecifier; 30 import android.net.TransportInfo; 31 import android.net.wifi.WifiInfo; 32 import android.os.Handler; 33 import android.os.SystemClock; 34 import android.telephony.SubscriptionInfo; 35 import android.telephony.SubscriptionManager; 36 import android.telephony.TelephonyManager; 37 import android.util.Log; 38 import android.util.SparseArray; 39 import android.util.SparseIntArray; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.metrics.DailykeepaliveInfoReported; 43 import com.android.metrics.DurationForNumOfKeepalive; 44 import com.android.metrics.DurationPerNumOfKeepalive; 45 import com.android.metrics.KeepaliveLifetimeForCarrier; 46 import com.android.metrics.KeepaliveLifetimePerCarrier; 47 import com.android.modules.utils.BackgroundThread; 48 import com.android.modules.utils.build.SdkLevel; 49 import com.android.net.module.util.CollectionUtils; 50 import com.android.server.ConnectivityStatsLog; 51 52 import java.util.ArrayList; 53 import java.util.HashMap; 54 import java.util.HashSet; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Objects; 58 import java.util.Set; 59 60 /** 61 * Tracks carrier and duration metrics of automatic on/off keepalives. 62 * 63 * <p>This class follows AutomaticOnOffKeepaliveTracker closely and its on*Keepalive methods needs 64 * to be called in a timely manner to keep the metrics accurate. It is also not thread-safe and all 65 * public methods must be called by the same thread, namely the ConnectivityService handler thread. 66 */ 67 public class KeepaliveStatsTracker { 68 private static final String TAG = KeepaliveStatsTracker.class.getSimpleName(); 69 70 @NonNull private final Handler mConnectivityServiceHandler; 71 @NonNull private final Dependencies mDependencies; 72 73 // Mapping of subId to carrierId. Updates are received from OnSubscriptionsChangedListener 74 private final SparseIntArray mCachedCarrierIdPerSubId = new SparseIntArray(); 75 // The default subscription id obtained from SubscriptionManager.getDefaultSubscriptionId. 76 // Updates are received from the ACTION_DEFAULT_SUBSCRIPTION_CHANGED broadcast. 77 private int mCachedDefaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 78 79 // Class to store network information, lifetime durations and active state of a keepalive. 80 private static final class KeepaliveStats { 81 // The carrier ID for a keepalive, or TelephonyManager.UNKNOWN_CARRIER_ID(-1) if not set. 82 public final int carrierId; 83 // The transport types of the underlying network for each keepalive. A network may include 84 // multiple transport types. Each transport type is represented by a different bit, defined 85 // in NetworkCapabilities 86 public final int transportTypes; 87 // The keepalive interval in millis. 88 public final int intervalMs; 89 // The uid of the app that requested the keepalive. 90 public final int appUid; 91 // Indicates if the keepalive is an automatic keepalive. 92 public final boolean isAutoKeepalive; 93 94 // Snapshot of the lifetime stats 95 public static class LifetimeStats { 96 public final int lifetimeMs; 97 public final int activeLifetimeMs; 98 LifetimeStats(int lifetimeMs, int activeLifetimeMs)99 LifetimeStats(int lifetimeMs, int activeLifetimeMs) { 100 this.lifetimeMs = lifetimeMs; 101 this.activeLifetimeMs = activeLifetimeMs; 102 } 103 } 104 105 // The total time since the keepalive is started until it is stopped. 106 private int mLifetimeMs = 0; 107 // The total time the keepalive is active (not suspended). 108 private int mActiveLifetimeMs = 0; 109 110 // A timestamp of the most recent time the lifetime metrics was updated. 111 private long mLastUpdateLifetimeTimestamp; 112 113 // A flag to indicate if the keepalive is active. 114 private boolean mKeepaliveActive = true; 115 116 /** 117 * Gets the lifetime stats for the keepalive, updated to timeNow, and then resets it. 118 * 119 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 120 */ getAndResetLifetimeStats(long timeNow)121 public LifetimeStats getAndResetLifetimeStats(long timeNow) { 122 updateLifetimeStatsAndSetActive(timeNow, mKeepaliveActive); 123 // Get a snapshot of the stats 124 final LifetimeStats lifetimeStats = new LifetimeStats(mLifetimeMs, mActiveLifetimeMs); 125 // Reset the stats 126 resetLifetimeStats(timeNow); 127 128 return lifetimeStats; 129 } 130 isKeepaliveActive()131 public boolean isKeepaliveActive() { 132 return mKeepaliveActive; 133 } 134 KeepaliveStats( int carrierId, int transportTypes, int intervalSeconds, int appUid, boolean isAutoKeepalive, long timeNow)135 KeepaliveStats( 136 int carrierId, 137 int transportTypes, 138 int intervalSeconds, 139 int appUid, 140 boolean isAutoKeepalive, 141 long timeNow) { 142 this.carrierId = carrierId; 143 this.transportTypes = transportTypes; 144 this.intervalMs = intervalSeconds * 1000; 145 this.appUid = appUid; 146 this.isAutoKeepalive = isAutoKeepalive; 147 mLastUpdateLifetimeTimestamp = timeNow; 148 } 149 150 /** 151 * Updates the lifetime metrics to the given time and sets the active state. This should be 152 * called whenever the active state of the keepalive changes. 153 * 154 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 155 */ updateLifetimeStatsAndSetActive(long timeNow, boolean keepaliveActive)156 public void updateLifetimeStatsAndSetActive(long timeNow, boolean keepaliveActive) { 157 final int durationIncrease = (int) (timeNow - mLastUpdateLifetimeTimestamp); 158 mLifetimeMs += durationIncrease; 159 if (mKeepaliveActive) mActiveLifetimeMs += durationIncrease; 160 161 mLastUpdateLifetimeTimestamp = timeNow; 162 mKeepaliveActive = keepaliveActive; 163 } 164 165 /** 166 * Resets the lifetime metrics but does not reset the active/stopped state of the keepalive. 167 * This also updates the time to timeNow, ensuring stats will start from this time. 168 * 169 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 170 */ resetLifetimeStats(long timeNow)171 public void resetLifetimeStats(long timeNow) { 172 mLifetimeMs = 0; 173 mActiveLifetimeMs = 0; 174 mLastUpdateLifetimeTimestamp = timeNow; 175 } 176 } 177 178 // List of duration stats metric where the index is the number of concurrent keepalives. 179 // Each DurationForNumOfKeepalive message stores a registered duration and an active duration. 180 // Registered duration is the total time spent with mNumRegisteredKeepalive == index. 181 // Active duration is the total time spent with mNumActiveKeepalive == index. 182 private final List<DurationForNumOfKeepalive.Builder> mDurationPerNumOfKeepalive = 183 new ArrayList<>(); 184 185 // Map of keepalives identified by the id from getKeepaliveId to their stats information. 186 private final SparseArray<KeepaliveStats> mKeepaliveStatsPerId = new SparseArray<>(); 187 188 // Generate a unique integer using a given network's netId and the slot number. 189 // This is possible because netId is a 16 bit integer, so an integer with the first 16 bits as 190 // the netId and the last 16 bits as the slot number can be created. This allows slot numbers to 191 // be up to 2^16. getKeepaliveId(@onNull Network network, int slot)192 private int getKeepaliveId(@NonNull Network network, int slot) { 193 final int netId = network.getNetId(); 194 if (netId < 0 || netId >= (1 << 16)) { 195 throw new IllegalArgumentException("Unexpected netId value: " + netId); 196 } 197 if (slot < 0 || slot >= (1 << 16)) { 198 throw new IllegalArgumentException("Unexpected slot value: " + slot); 199 } 200 201 return (netId << 16) + slot; 202 } 203 204 // Class to act as the key to aggregate the KeepaliveLifetimeForCarrier stats. 205 private static final class LifetimeKey { 206 public final int carrierId; 207 public final int transportTypes; 208 public final int intervalMs; 209 LifetimeKey(int carrierId, int transportTypes, int intervalMs)210 LifetimeKey(int carrierId, int transportTypes, int intervalMs) { 211 this.carrierId = carrierId; 212 this.transportTypes = transportTypes; 213 this.intervalMs = intervalMs; 214 } 215 216 @Override equals(Object o)217 public boolean equals(Object o) { 218 if (this == o) return true; 219 if (o == null || getClass() != o.getClass()) return false; 220 221 final LifetimeKey that = (LifetimeKey) o; 222 223 return carrierId == that.carrierId && transportTypes == that.transportTypes 224 && intervalMs == that.intervalMs; 225 } 226 227 @Override hashCode()228 public int hashCode() { 229 return carrierId + 3 * transportTypes + 5 * intervalMs; 230 } 231 } 232 233 // Map to aggregate the KeepaliveLifetimeForCarrier stats using LifetimeKey as the key. 234 final Map<LifetimeKey, KeepaliveLifetimeForCarrier.Builder> mAggregateKeepaliveLifetime = 235 new HashMap<>(); 236 237 private final Set<Integer> mAppUids = new HashSet<Integer>(); 238 private int mNumKeepaliveRequests = 0; 239 private int mNumAutomaticKeepaliveRequests = 0; 240 241 private int mNumRegisteredKeepalive = 0; 242 private int mNumActiveKeepalive = 0; 243 244 // A timestamp of the most recent time the duration metrics was updated. 245 private long mLastUpdateDurationsTimestamp; 246 247 /** Dependency class */ 248 @VisibleForTesting 249 public static class Dependencies { 250 // Returns a timestamp with the time base of SystemClock.elapsedRealtime to keep durations 251 // relative to start time and avoid timezone change, including time spent in deep sleep. getElapsedRealtime()252 public long getElapsedRealtime() { 253 return SystemClock.elapsedRealtime(); 254 } 255 256 /** 257 * Writes a DAILY_KEEPALIVE_INFO_REPORTED to ConnectivityStatsLog. 258 * 259 * @param dailyKeepaliveInfoReported the proto to write to statsD. 260 */ writeStats(DailykeepaliveInfoReported dailyKeepaliveInfoReported)261 public void writeStats(DailykeepaliveInfoReported dailyKeepaliveInfoReported) { 262 ConnectivityStatsLog.write( 263 ConnectivityStatsLog.DAILY_KEEPALIVE_INFO_REPORTED, 264 dailyKeepaliveInfoReported.getDurationPerNumOfKeepalive().toByteArray(), 265 dailyKeepaliveInfoReported.getKeepaliveLifetimePerCarrier().toByteArray(), 266 dailyKeepaliveInfoReported.getKeepaliveRequests(), 267 dailyKeepaliveInfoReported.getAutomaticKeepaliveRequests(), 268 dailyKeepaliveInfoReported.getDistinctUserCount(), 269 CollectionUtils.toIntArray(dailyKeepaliveInfoReported.getUidList())); 270 } 271 } 272 KeepaliveStatsTracker(@onNull Context context, @NonNull Handler handler)273 public KeepaliveStatsTracker(@NonNull Context context, @NonNull Handler handler) { 274 this(context, handler, new Dependencies()); 275 } 276 277 @VisibleForTesting KeepaliveStatsTracker( @onNull Context context, @NonNull Handler handler, @NonNull Dependencies dependencies)278 public KeepaliveStatsTracker( 279 @NonNull Context context, 280 @NonNull Handler handler, 281 @NonNull Dependencies dependencies) { 282 Objects.requireNonNull(context); 283 mDependencies = Objects.requireNonNull(dependencies); 284 mConnectivityServiceHandler = Objects.requireNonNull(handler); 285 286 final SubscriptionManager subscriptionManager = 287 Objects.requireNonNull(context.getSystemService(SubscriptionManager.class)); 288 289 mLastUpdateDurationsTimestamp = mDependencies.getElapsedRealtime(); 290 context.registerReceiver( 291 new BroadcastReceiver() { 292 @Override 293 public void onReceive(Context context, Intent intent) { 294 mCachedDefaultSubscriptionId = 295 intent.getIntExtra( 296 SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 297 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 298 } 299 }, 300 new IntentFilter(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED), 301 /* broadcastPermission= */ null, 302 mConnectivityServiceHandler); 303 304 // The default constructor for OnSubscriptionsChangedListener will always implicitly grab 305 // the looper of the current thread. In the case the current thread does not have a looper, 306 // this will throw. Therefore, post a runnable that creates it there. 307 // When the callback is called on the BackgroundThread, post a message on the CS handler 308 // thread to update the caches, which can only be touched there. 309 BackgroundThread.getHandler().post(() -> 310 subscriptionManager.addOnSubscriptionsChangedListener( 311 r -> r.run(), new OnSubscriptionsChangedListener() { 312 @Override 313 public void onSubscriptionsChanged() { 314 final List<SubscriptionInfo> activeSubInfoList = 315 subscriptionManager.getActiveSubscriptionInfoList(); 316 // A null subInfo list here indicates the current state is unknown 317 // but not necessarily empty, simply ignore it. Another call to the 318 // listener will be invoked in the future. 319 if (activeSubInfoList == null) return; 320 mConnectivityServiceHandler.post(() -> { 321 mCachedCarrierIdPerSubId.clear(); 322 323 for (final SubscriptionInfo subInfo : activeSubInfoList) { 324 mCachedCarrierIdPerSubId.put(subInfo.getSubscriptionId(), 325 subInfo.getCarrierId()); 326 } 327 }); 328 } 329 })); 330 } 331 332 /** Ensures the list of duration metrics is large enough for number of registered keepalives. */ ensureDurationPerNumOfKeepaliveSize()333 private void ensureDurationPerNumOfKeepaliveSize() { 334 if (mNumActiveKeepalive < 0 || mNumRegisteredKeepalive < 0) { 335 throw new IllegalStateException( 336 "Number of active or registered keepalives is negative"); 337 } 338 if (mNumActiveKeepalive > mNumRegisteredKeepalive) { 339 throw new IllegalStateException( 340 "Number of active keepalives greater than registered keepalives"); 341 } 342 343 while (mDurationPerNumOfKeepalive.size() <= mNumRegisteredKeepalive) { 344 final DurationForNumOfKeepalive.Builder durationForNumOfKeepalive = 345 DurationForNumOfKeepalive.newBuilder(); 346 durationForNumOfKeepalive.setNumOfKeepalive(mDurationPerNumOfKeepalive.size()); 347 durationForNumOfKeepalive.setKeepaliveRegisteredDurationsMsec(0); 348 durationForNumOfKeepalive.setKeepaliveActiveDurationsMsec(0); 349 350 mDurationPerNumOfKeepalive.add(durationForNumOfKeepalive); 351 } 352 } 353 354 /** 355 * Updates the durations metrics to the given time. This should always be called before making a 356 * change to mNumRegisteredKeepalive or mNumActiveKeepalive to keep the duration metrics 357 * correct. 358 * 359 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 360 */ updateDurationsPerNumOfKeepalive(long timeNow)361 private void updateDurationsPerNumOfKeepalive(long timeNow) { 362 if (mDurationPerNumOfKeepalive.size() < mNumRegisteredKeepalive) { 363 Log.e(TAG, "Unexpected jump in number of registered keepalive"); 364 } 365 ensureDurationPerNumOfKeepaliveSize(); 366 367 final int durationIncrease = (int) (timeNow - mLastUpdateDurationsTimestamp); 368 final DurationForNumOfKeepalive.Builder durationForNumOfRegisteredKeepalive = 369 mDurationPerNumOfKeepalive.get(mNumRegisteredKeepalive); 370 371 durationForNumOfRegisteredKeepalive.setKeepaliveRegisteredDurationsMsec( 372 durationForNumOfRegisteredKeepalive.getKeepaliveRegisteredDurationsMsec() 373 + durationIncrease); 374 375 final DurationForNumOfKeepalive.Builder durationForNumOfActiveKeepalive = 376 mDurationPerNumOfKeepalive.get(mNumActiveKeepalive); 377 378 durationForNumOfActiveKeepalive.setKeepaliveActiveDurationsMsec( 379 durationForNumOfActiveKeepalive.getKeepaliveActiveDurationsMsec() 380 + durationIncrease); 381 382 mLastUpdateDurationsTimestamp = timeNow; 383 } 384 385 // TODO: Move this function to frameworks/libs/net/.../NetworkCapabilitiesUtils.java getSubId(@onNull NetworkCapabilities nc, int defaultSubId)386 private static int getSubId(@NonNull NetworkCapabilities nc, int defaultSubId) { 387 if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { 388 final NetworkSpecifier networkSpecifier = nc.getNetworkSpecifier(); 389 if (networkSpecifier instanceof TelephonyNetworkSpecifier) { 390 return ((TelephonyNetworkSpecifier) networkSpecifier).getSubscriptionId(); 391 } 392 // Use the default subscriptionId. 393 return defaultSubId; 394 } 395 if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { 396 final TransportInfo info = nc.getTransportInfo(); 397 if (info instanceof WifiInfo) { 398 return ((WifiInfo) info).getSubscriptionId(); 399 } 400 } 401 402 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 403 } 404 getCarrierId(@onNull NetworkCapabilities networkCapabilities)405 private int getCarrierId(@NonNull NetworkCapabilities networkCapabilities) { 406 // Try to get the correct subscription id. 407 final int subId = getSubId(networkCapabilities, mCachedDefaultSubscriptionId); 408 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 409 return TelephonyManager.UNKNOWN_CARRIER_ID; 410 } 411 return mCachedCarrierIdPerSubId.get(subId, TelephonyManager.UNKNOWN_CARRIER_ID); 412 } 413 getTransportTypes(@onNull NetworkCapabilities networkCapabilities)414 private int getTransportTypes(@NonNull NetworkCapabilities networkCapabilities) { 415 // Transport types are internally packed as bits starting from bit 0. Casting to int works 416 // fine since for now and the foreseeable future, there will be less than 32 transports. 417 return (int) networkCapabilities.getTransportTypesInternal(); 418 } 419 420 /** Inform the KeepaliveStatsTracker a keepalive has just started and is active. */ onStartKeepalive( @onNull Network network, int slot, @NonNull NetworkCapabilities nc, int intervalSeconds, int appUid, boolean isAutoKeepalive)421 public void onStartKeepalive( 422 @NonNull Network network, 423 int slot, 424 @NonNull NetworkCapabilities nc, 425 int intervalSeconds, 426 int appUid, 427 boolean isAutoKeepalive) { 428 ensureRunningOnHandlerThread(); 429 final int keepaliveId = getKeepaliveId(network, slot); 430 if (mKeepaliveStatsPerId.contains(keepaliveId)) { 431 throw new IllegalArgumentException( 432 "Attempt to start keepalive stats on a known network, slot pair"); 433 } 434 435 mNumKeepaliveRequests++; 436 if (isAutoKeepalive) mNumAutomaticKeepaliveRequests++; 437 mAppUids.add(appUid); 438 439 final long timeNow = mDependencies.getElapsedRealtime(); 440 updateDurationsPerNumOfKeepalive(timeNow); 441 442 mNumRegisteredKeepalive++; 443 mNumActiveKeepalive++; 444 445 final KeepaliveStats newKeepaliveStats = 446 new KeepaliveStats( 447 getCarrierId(nc), 448 getTransportTypes(nc), 449 intervalSeconds, 450 appUid, 451 isAutoKeepalive, 452 timeNow); 453 454 mKeepaliveStatsPerId.put(keepaliveId, newKeepaliveStats); 455 } 456 457 /** 458 * Inform the KeepaliveStatsTracker that the keepalive with the given network, slot pair has 459 * updated its active state to keepaliveActive. 460 * 461 * @return the KeepaliveStats associated with the network, slot pair or null if it is unknown. 462 */ onKeepaliveActive( @onNull Network network, int slot, boolean keepaliveActive)463 private @NonNull KeepaliveStats onKeepaliveActive( 464 @NonNull Network network, int slot, boolean keepaliveActive) { 465 final long timeNow = mDependencies.getElapsedRealtime(); 466 return onKeepaliveActive(network, slot, keepaliveActive, timeNow); 467 } 468 469 /** 470 * Inform the KeepaliveStatsTracker that the keepalive with the given network, slot pair has 471 * updated its active state to keepaliveActive. 472 * 473 * @param network the network of the keepalive 474 * @param slot the slot number of the keepalive 475 * @param keepaliveActive the new active state of the keepalive 476 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 477 * @return the KeepaliveStats associated with the network, slot pair or null if it is unknown. 478 */ onKeepaliveActive( @onNull Network network, int slot, boolean keepaliveActive, long timeNow)479 private @NonNull KeepaliveStats onKeepaliveActive( 480 @NonNull Network network, int slot, boolean keepaliveActive, long timeNow) { 481 ensureRunningOnHandlerThread(); 482 483 final int keepaliveId = getKeepaliveId(network, slot); 484 if (!mKeepaliveStatsPerId.contains(keepaliveId)) { 485 throw new IllegalArgumentException( 486 "Attempt to set active keepalive on an unknown network, slot pair"); 487 } 488 updateDurationsPerNumOfKeepalive(timeNow); 489 490 final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.get(keepaliveId); 491 if (keepaliveActive != keepaliveStats.isKeepaliveActive()) { 492 mNumActiveKeepalive += keepaliveActive ? 1 : -1; 493 } 494 495 keepaliveStats.updateLifetimeStatsAndSetActive(timeNow, keepaliveActive); 496 return keepaliveStats; 497 } 498 499 /** Inform the KeepaliveStatsTracker a keepalive has just been paused. */ onPauseKeepalive(@onNull Network network, int slot)500 public void onPauseKeepalive(@NonNull Network network, int slot) { 501 onKeepaliveActive(network, slot, /* keepaliveActive= */ false); 502 } 503 504 /** Inform the KeepaliveStatsTracker a keepalive has just been resumed. */ onResumeKeepalive(@onNull Network network, int slot)505 public void onResumeKeepalive(@NonNull Network network, int slot) { 506 onKeepaliveActive(network, slot, /* keepaliveActive= */ true); 507 } 508 509 /** Inform the KeepaliveStatsTracker a keepalive has just been stopped. */ onStopKeepalive(@onNull Network network, int slot)510 public void onStopKeepalive(@NonNull Network network, int slot) { 511 final int keepaliveId = getKeepaliveId(network, slot); 512 final long timeNow = mDependencies.getElapsedRealtime(); 513 514 final KeepaliveStats keepaliveStats = 515 onKeepaliveActive(network, slot, /* keepaliveActive= */ false, timeNow); 516 517 mNumRegisteredKeepalive--; 518 519 // add to the aggregate since it will be removed. 520 addToAggregateKeepaliveLifetime(keepaliveStats, timeNow); 521 // free up the slot. 522 mKeepaliveStatsPerId.remove(keepaliveId); 523 } 524 525 /** 526 * Updates and adds the lifetime metric of keepaliveStats to the aggregate. 527 * 528 * @param keepaliveStats the stats to add to the aggregate 529 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 530 */ addToAggregateKeepaliveLifetime( @onNull KeepaliveStats keepaliveStats, long timeNow)531 private void addToAggregateKeepaliveLifetime( 532 @NonNull KeepaliveStats keepaliveStats, long timeNow) { 533 534 final KeepaliveStats.LifetimeStats lifetimeStats = 535 keepaliveStats.getAndResetLifetimeStats(timeNow); 536 537 final LifetimeKey key = 538 new LifetimeKey( 539 keepaliveStats.carrierId, 540 keepaliveStats.transportTypes, 541 keepaliveStats.intervalMs); 542 543 KeepaliveLifetimeForCarrier.Builder keepaliveLifetimeForCarrier = 544 mAggregateKeepaliveLifetime.get(key); 545 546 if (keepaliveLifetimeForCarrier == null) { 547 keepaliveLifetimeForCarrier = 548 KeepaliveLifetimeForCarrier.newBuilder() 549 .setCarrierId(keepaliveStats.carrierId) 550 .setTransportTypes(keepaliveStats.transportTypes) 551 .setIntervalsMsec(keepaliveStats.intervalMs); 552 mAggregateKeepaliveLifetime.put(key, keepaliveLifetimeForCarrier); 553 } 554 555 keepaliveLifetimeForCarrier.setLifetimeMsec( 556 keepaliveLifetimeForCarrier.getLifetimeMsec() + lifetimeStats.lifetimeMs); 557 keepaliveLifetimeForCarrier.setActiveLifetimeMsec( 558 keepaliveLifetimeForCarrier.getActiveLifetimeMsec() 559 + lifetimeStats.activeLifetimeMs); 560 } 561 562 /** 563 * Builds and returns DailykeepaliveInfoReported proto. 564 * 565 * @return the DailykeepaliveInfoReported proto that was built. 566 */ 567 @VisibleForTesting buildKeepaliveMetrics()568 public @NonNull DailykeepaliveInfoReported buildKeepaliveMetrics() { 569 ensureRunningOnHandlerThread(); 570 final long timeNow = mDependencies.getElapsedRealtime(); 571 return buildKeepaliveMetrics(timeNow); 572 } 573 574 /** 575 * Updates the metrics to timeNow and builds and returns DailykeepaliveInfoReported proto. 576 * 577 * @param timeNow a timestamp obtained using Dependencies.getElapsedRealtime 578 */ buildKeepaliveMetrics(long timeNow)579 private @NonNull DailykeepaliveInfoReported buildKeepaliveMetrics(long timeNow) { 580 updateDurationsPerNumOfKeepalive(timeNow); 581 582 final DurationPerNumOfKeepalive.Builder durationPerNumOfKeepalive = 583 DurationPerNumOfKeepalive.newBuilder(); 584 585 mDurationPerNumOfKeepalive.forEach( 586 durationForNumOfKeepalive -> 587 durationPerNumOfKeepalive.addDurationForNumOfKeepalive( 588 durationForNumOfKeepalive)); 589 590 final KeepaliveLifetimePerCarrier.Builder keepaliveLifetimePerCarrier = 591 KeepaliveLifetimePerCarrier.newBuilder(); 592 593 for (int i = 0; i < mKeepaliveStatsPerId.size(); i++) { 594 final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.valueAt(i); 595 addToAggregateKeepaliveLifetime(keepaliveStats, timeNow); 596 } 597 598 // Fill keepalive carrier stats to the proto 599 mAggregateKeepaliveLifetime 600 .values() 601 .forEach( 602 keepaliveLifetimeForCarrier -> 603 keepaliveLifetimePerCarrier.addKeepaliveLifetimeForCarrier( 604 keepaliveLifetimeForCarrier)); 605 606 final DailykeepaliveInfoReported.Builder dailyKeepaliveInfoReported = 607 DailykeepaliveInfoReported.newBuilder(); 608 609 dailyKeepaliveInfoReported.setDurationPerNumOfKeepalive(durationPerNumOfKeepalive); 610 dailyKeepaliveInfoReported.setKeepaliveLifetimePerCarrier(keepaliveLifetimePerCarrier); 611 dailyKeepaliveInfoReported.setKeepaliveRequests(mNumKeepaliveRequests); 612 dailyKeepaliveInfoReported.setAutomaticKeepaliveRequests(mNumAutomaticKeepaliveRequests); 613 dailyKeepaliveInfoReported.setDistinctUserCount(mAppUids.size()); 614 dailyKeepaliveInfoReported.addAllUid(mAppUids); 615 616 return dailyKeepaliveInfoReported.build(); 617 } 618 619 /** 620 * Builds and resets the stored metrics. Similar to buildKeepaliveMetrics but also resets the 621 * metrics while maintaining the state of the keepalives. 622 * 623 * @return the DailykeepaliveInfoReported proto that was built. 624 */ 625 @VisibleForTesting buildAndResetMetrics()626 public @NonNull DailykeepaliveInfoReported buildAndResetMetrics() { 627 ensureRunningOnHandlerThread(); 628 final long timeNow = mDependencies.getElapsedRealtime(); 629 630 final DailykeepaliveInfoReported metrics = buildKeepaliveMetrics(timeNow); 631 632 mDurationPerNumOfKeepalive.clear(); 633 mAggregateKeepaliveLifetime.clear(); 634 mAppUids.clear(); 635 mNumKeepaliveRequests = 0; 636 mNumAutomaticKeepaliveRequests = 0; 637 638 // Update the metrics with the existing keepalives. 639 ensureDurationPerNumOfKeepaliveSize(); 640 641 mAggregateKeepaliveLifetime.clear(); 642 // Reset the stats for existing keepalives 643 for (int i = 0; i < mKeepaliveStatsPerId.size(); i++) { 644 final KeepaliveStats keepaliveStats = mKeepaliveStatsPerId.valueAt(i); 645 keepaliveStats.resetLifetimeStats(timeNow); 646 mAppUids.add(keepaliveStats.appUid); 647 mNumKeepaliveRequests++; 648 if (keepaliveStats.isAutoKeepalive) mNumAutomaticKeepaliveRequests++; 649 } 650 651 return metrics; 652 } 653 654 /** Writes the stored metrics to ConnectivityStatsLog and resets. */ writeAndResetMetrics()655 public void writeAndResetMetrics() { 656 ensureRunningOnHandlerThread(); 657 // Keepalive stats use repeated atoms, which are only supported on T+. If written to statsd 658 // on S- they will bootloop the system, so they must not be sent on S-. See b/289471411. 659 if (!SdkLevel.isAtLeastT()) { 660 Log.d(TAG, "KeepaliveStatsTracker is disabled before T, skipping write"); 661 return; 662 } 663 664 final DailykeepaliveInfoReported dailyKeepaliveInfoReported = buildAndResetMetrics(); 665 mDependencies.writeStats(dailyKeepaliveInfoReported); 666 } 667 ensureRunningOnHandlerThread()668 private void ensureRunningOnHandlerThread() { 669 if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) { 670 throw new IllegalStateException( 671 "Not running on handler thread: " + Thread.currentThread().getName()); 672 } 673 } 674 } 675