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