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