1 /*
2  * Copyright (C) 2016 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.devicepolicy;
18 
19 import android.app.AlarmManager;
20 import android.app.AlarmManager.OnAlarmListener;
21 import android.app.admin.DeviceAdminReceiver;
22 import android.app.admin.NetworkEvent;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.os.SystemClock;
28 import android.util.LongSparseArray;
29 import android.util.Slog;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 
37 /**
38  * A Handler class for managing network logging on a background thread.
39  */
40 final class NetworkLoggingHandler extends Handler {
41 
42     private static final String TAG = NetworkLoggingHandler.class.getSimpleName();
43 
44     static final String NETWORK_EVENT_KEY = "network_event";
45 
46     // If this value changes, update DevicePolicyManager#retrieveNetworkLogs() javadoc
47     private static final int MAX_EVENTS_PER_BATCH = 1200;
48 
49     /**
50      * Maximum number of batches to store in memory. If more batches are generated and the DO
51      * doesn't fetch them, we will discard the oldest one.
52      */
53     private static final int MAX_BATCHES = 5;
54 
55     private static final long BATCH_FINALIZATION_TIMEOUT_MS = 90 * 60 * 1000; // 1.5h
56     private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS = 30 * 60 * 1000; // 30m
57 
58     private static final String NETWORK_LOGGING_TIMEOUT_ALARM_TAG = "NetworkLogging.batchTimeout";
59 
60     /** Delay after which older batches get discarded after a retrieval. */
61     private static final long RETRIEVED_BATCH_DISCARD_DELAY_MS = 5 * 60 * 1000; // 5m
62 
63     /** Do not call into mDpm with locks held */
64     private final DevicePolicyManagerService mDpm;
65     private final AlarmManager mAlarmManager;
66 
67     private long mId;
68 
69     private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
70         @Override
71         public void onAlarm() {
72             Slog.d(TAG, "Received a batch finalization timeout alarm, finalizing "
73                     + mNetworkEvents.size() + " pending events.");
74             Bundle notificationExtras = null;
75             synchronized (NetworkLoggingHandler.this) {
76                 notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
77             }
78             if (notificationExtras != null) {
79                 notifyDeviceOwner(notificationExtras);
80             }
81         }
82     };
83 
84     @VisibleForTesting
85     static final int LOG_NETWORK_EVENT_MSG = 1;
86 
87     /** Network events accumulated so far to be finalized into a batch at some point. */
88     @GuardedBy("this")
89     private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
90 
91     /**
92      * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already
93      * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
94      */
95     @GuardedBy("this")
96     private final LongSparseArray<ArrayList<NetworkEvent>> mBatches =
97             new LongSparseArray<>(MAX_BATCHES);
98 
99     @GuardedBy("this")
100     private boolean mPaused = false;
101 
102     // each full batch is represented by its token, which the DPC has to provide back to retrieve it
103     @GuardedBy("this")
104     private long mCurrentBatchToken;
105 
106     @GuardedBy("this")
107     private long mLastRetrievedBatchToken;
108 
NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm)109     NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
110         this(looper, dpm, 0 /* event id */);
111     }
112 
113     @VisibleForTesting
NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id)114     NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) {
115         super(looper);
116         this.mDpm = dpm;
117         this.mAlarmManager = mDpm.mInjector.getAlarmManager();
118         this.mId = id;
119     }
120 
121     @Override
handleMessage(Message msg)122     public void handleMessage(Message msg) {
123         switch (msg.what) {
124             case LOG_NETWORK_EVENT_MSG: {
125                 final NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY);
126                 if (networkEvent != null) {
127                     Bundle notificationExtras = null;
128                     synchronized (NetworkLoggingHandler.this) {
129                         mNetworkEvents.add(networkEvent);
130                         if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
131                             notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
132                         }
133                     }
134                     if (notificationExtras != null) {
135                         notifyDeviceOwner(notificationExtras);
136                     }
137                 }
138                 break;
139             }
140             default: {
141                 Slog.d(TAG, "NetworkLoggingHandler received an unknown of message.");
142                 break;
143             }
144         }
145     }
146 
scheduleBatchFinalization()147     void scheduleBatchFinalization() {
148         final long when = SystemClock.elapsedRealtime() + BATCH_FINALIZATION_TIMEOUT_MS;
149         // We use alarm manager and not just postDelayed here to ensure the batch gets finalized
150         // even if the device goes to sleep.
151         mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
152                 BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS, NETWORK_LOGGING_TIMEOUT_ALARM_TAG,
153                 mBatchTimeoutAlarmListener, this);
154         Slog.d(TAG, "Scheduled a new batch finalization alarm " + BATCH_FINALIZATION_TIMEOUT_MS
155                 + "ms from now.");
156     }
157 
pause()158     synchronized void pause() {
159         Slog.d(TAG, "Paused network logging");
160         mPaused = true;
161     }
162 
resume()163     void resume() {
164         Bundle notificationExtras = null;
165         synchronized (this) {
166             if (!mPaused) {
167                 Slog.d(TAG, "Attempted to resume network logging, but logging is not paused.");
168                 return;
169             }
170 
171             Slog.d(TAG, "Resumed network logging. Current batch=" + mCurrentBatchToken
172                     + ", LastRetrievedBatch=" + mLastRetrievedBatchToken);
173             mPaused = false;
174 
175             // If there is a batch ready that the device owner hasn't been notified about, do it now.
176             if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) {
177                 scheduleBatchFinalization();
178                 notificationExtras = buildDeviceOwnerMessageLocked();
179             }
180         }
181         if (notificationExtras != null) {
182             notifyDeviceOwner(notificationExtras);
183         }
184     }
185 
discardLogs()186     synchronized void discardLogs() {
187         mBatches.clear();
188         mNetworkEvents = new ArrayList<>();
189         Slog.d(TAG, "Discarded all network logs");
190     }
191 
192     @GuardedBy("this")
193     /** @returns extras if a message should be sent to the device owner */
finalizeBatchAndBuildDeviceOwnerMessageLocked()194     private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
195         Bundle notificationExtras = null;
196         if (mNetworkEvents.size() > 0) {
197             // Assign ids to the events.
198             for (NetworkEvent event : mNetworkEvents) {
199                 event.setId(mId);
200                 if (mId == Long.MAX_VALUE) {
201                     Slog.i(TAG, "Reached maximum id value; wrapping around ." + mCurrentBatchToken);
202                     mId = 0;
203                 } else {
204                     mId++;
205                 }
206             }
207             // Finalize the batch and start a new one from scratch.
208             if (mBatches.size() >= MAX_BATCHES) {
209                 // Remove the oldest batch if we hit the limit.
210                 mBatches.removeAt(0);
211             }
212             mCurrentBatchToken++;
213             mBatches.append(mCurrentBatchToken, mNetworkEvents);
214             mNetworkEvents = new ArrayList<>();
215             if (!mPaused) {
216                 notificationExtras = buildDeviceOwnerMessageLocked();
217             }
218         } else {
219             // Don't notify the DO, since there are no events; DPC can still retrieve
220             // the last full batch if not paused.
221             Slog.d(TAG, "Was about to finalize the batch, but there were no events to send to"
222                     + " the DPC, the batchToken of last available batch: " + mCurrentBatchToken);
223         }
224         // Regardless of whether the batch was non-empty schedule a new finalization after timeout.
225         scheduleBatchFinalization();
226         return notificationExtras;
227     }
228 
229     @GuardedBy("this")
230     /** Build extras notification to the DO. Should only be called when there
231         is a batch available. */
buildDeviceOwnerMessageLocked()232     private Bundle buildDeviceOwnerMessageLocked() {
233         final Bundle extras = new Bundle();
234         final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size();
235         extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken);
236         extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, lastBatchSize);
237         return extras;
238     }
239 
240     /** Sends a notification to the DO. Should not hold locks as DevicePolicyManagerService may
241         call into NetworkLoggingHandler. */
notifyDeviceOwner(Bundle extras)242     private void notifyDeviceOwner(Bundle extras) {
243         Slog.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
244                 + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1));
245         if (Thread.holdsLock(this)) {
246             Slog.wtfStack(TAG, "Shouldn't be called with NetworkLoggingHandler lock held");
247             return;
248         }
249         mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
250     }
251 
retrieveFullLogBatch(final long batchToken)252     synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) {
253         final int index = mBatches.indexOfKey(batchToken);
254         if (index < 0) {
255             // Invalid token or batch has already been discarded.
256             return null;
257         }
258 
259         // Schedule this and older batches to be discarded after a delay to lessen memory load
260         // without interfering with the admin's ability to collect logs out-of-order.
261         // It isn't critical and we allow it to be delayed further if the phone sleeps, so we don't
262         // use the alarm manager here.
263         postDelayed(() -> {
264             synchronized(this) {
265                 while (mBatches.size() > 0 && mBatches.keyAt(0) <= batchToken) {
266                     mBatches.removeAt(0);
267                 }
268             }
269         }, RETRIEVED_BATCH_DISCARD_DELAY_MS);
270 
271         mLastRetrievedBatchToken = batchToken;
272         return mBatches.valueAt(index);
273     }
274 }
275 
276