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