Home | History | Annotate | Download | only in devicepolicy
      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 
    109     NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
    110         this(looper, dpm, 0 /* event id */);
    111     }
    112 
    113     @VisibleForTesting
    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
    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 
    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 
    158     synchronized void pause() {
    159         Slog.d(TAG, "Paused network logging");
    160         mPaused = true;
    161     }
    162 
    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 
    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 */
    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. */
    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. */
    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 
    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