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