• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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