• 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 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