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