summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/security/forensic/ForensicEvent.java8
-rw-r--r--services/core/java/com/android/server/security/forensic/DataAggregator.java142
-rw-r--r--services/core/java/com/android/server/security/forensic/DataSource.java29
-rw-r--r--services/core/java/com/android/server/security/forensic/ForensicService.java31
-rw-r--r--services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java103
5 files changed, 312 insertions, 1 deletions
diff --git a/core/java/android/security/forensic/ForensicEvent.java b/core/java/android/security/forensic/ForensicEvent.java
index 9cbc5ecea962..90906edcc636 100644
--- a/core/java/android/security/forensic/ForensicEvent.java
+++ b/core/java/android/security/forensic/ForensicEvent.java
@@ -61,6 +61,14 @@ public final class ForensicEvent implements Parcelable {
in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class);
}
+ public String getType() {
+ return mType;
+ }
+
+ public Map<String, String> getKeyValuePairs() {
+ return mKeyValuePairs;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeString(mType);
diff --git a/services/core/java/com/android/server/security/forensic/DataAggregator.java b/services/core/java/com/android/server/security/forensic/DataAggregator.java
new file mode 100644
index 000000000000..0079818e733b
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/DataAggregator.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.forensic;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.security.forensic.ForensicEvent;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DataAggregator {
+ private static final String TAG = "Forensic DataAggregator";
+ private static final int MSG_SINGLE_DATA = 0;
+ private static final int MSG_BATCH_DATA = 1;
+ private static final int MSG_DISABLE = 2;
+
+ private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
+ private final ForensicService mForensicService;
+ private final ArrayList<DataSource> mDataSources;
+
+ private List<ForensicEvent> mStoredEvents = new ArrayList<>();
+ private ServiceThread mHandlerThread;
+ private Handler mHandler;
+ public DataAggregator(ForensicService forensicService) {
+ mForensicService = forensicService;
+ mDataSources = new ArrayList<DataSource>();
+ }
+
+ @VisibleForTesting
+ void setHandler(Looper looper, ServiceThread serviceThread) {
+ mHandlerThread = serviceThread;
+ mHandler = new EventHandler(looper, this);
+ }
+
+ /**
+ * Initialize DataSources
+ * @return Whether the initialization succeeds.
+ */
+ // TODO: Add the corresponding data sources
+ public boolean initialize() {
+ return true;
+ }
+
+ /**
+ * Enable the data collection of all DataSources.
+ */
+ public void enable() {
+ mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND,
+ /* allowIo */ false);
+ mHandlerThread.start();
+ mHandler = new EventHandler(mHandlerThread.getLooper(), this);
+ for (DataSource ds : mDataSources) {
+ ds.enable();
+ }
+ }
+
+ /**
+ * DataSource calls it to transmit a single event.
+ */
+ public void addSingleData(ForensicEvent event) {
+ mHandler.obtainMessage(MSG_SINGLE_DATA, event).sendToTarget();
+ }
+
+ /**
+ * DataSource calls it to transmit list of events.
+ */
+ public void addBatchData(List<ForensicEvent> events) {
+ mHandler.obtainMessage(MSG_BATCH_DATA, events).sendToTarget();
+ }
+
+ /**
+ * Disable the data collection of all DataSources.
+ */
+ public void disable() {
+ mHandler.obtainMessage(MSG_DISABLE).sendToTarget();
+ }
+
+ private void onNewSingleData(ForensicEvent event) {
+ if (mStoredEvents.size() < STORED_EVENTS_SIZE_LIMIT) {
+ mStoredEvents.add(event);
+ } else {
+ mForensicService.addNewData(mStoredEvents);
+ mStoredEvents = new ArrayList<>();
+ }
+ }
+
+ private void onNewBatchData(List<ForensicEvent> events) {
+ mForensicService.addNewData(events);
+ }
+
+ private void onDisable() {
+ for (DataSource ds : mDataSources) {
+ ds.disable();
+ }
+ mHandlerThread.quitSafely();
+ mHandlerThread = null;
+ }
+
+ private static class EventHandler extends Handler {
+ private final DataAggregator mDataAggregator;
+ EventHandler(Looper looper, DataAggregator dataAggregator) {
+ super(looper);
+ mDataAggregator = dataAggregator;
+ }
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SINGLE_DATA:
+ mDataAggregator.onNewSingleData((ForensicEvent) msg.obj);
+ break;
+ case MSG_BATCH_DATA:
+ mDataAggregator.onNewBatchData((List<ForensicEvent>) msg.obj);
+ break;
+ case MSG_DISABLE:
+ mDataAggregator.onDisable();
+ break;
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/forensic/DataSource.java b/services/core/java/com/android/server/security/forensic/DataSource.java
new file mode 100644
index 000000000000..da7ee210281b
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/DataSource.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.forensic;
+
+public interface DataSource {
+ /**
+ * Enable the data collection.
+ */
+ void enable();
+
+ /**
+ * Disable the data collection.
+ */
+ void disable();
+}
diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java
index 20c648eb61c2..53b07c04139c 100644
--- a/services/core/java/com/android/server/security/forensic/ForensicService.java
+++ b/services/core/java/com/android/server/security/forensic/ForensicService.java
@@ -22,6 +22,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.security.forensic.ForensicEvent;
import android.security.forensic.IForensicService;
import android.security.forensic.IForensicServiceCommandCallback;
import android.security.forensic.IForensicServiceStateCallback;
@@ -32,6 +33,7 @@ import com.android.server.ServiceThread;
import com.android.server.SystemService;
import java.util.ArrayList;
+import java.util.List;
/**
* @hide
@@ -64,6 +66,7 @@ public class ForensicService extends SystemService {
private final Context mContext;
private final Handler mHandler;
private final BackupTransportConnection mBackupTransportConnection;
+ private final DataAggregator mDataAggregator;
private final BinderService mBinderService;
private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
@@ -79,6 +82,7 @@ public class ForensicService extends SystemService {
mContext = injector.getContext();
mHandler = new EventHandler(injector.getLooper(), this);
mBackupTransportConnection = injector.getBackupTransportConnection();
+ mDataAggregator = injector.getDataAggregator(this);
mBinderService = new BinderService(this);
}
@@ -167,6 +171,9 @@ public class ForensicService extends SystemService {
Slog.e(TAG, "RemoteException", e);
}
break;
+ case MSG_BACKUP:
+ mService.backup((List<ForensicEvent>) msg.obj);
+ break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
}
@@ -192,6 +199,10 @@ public class ForensicService extends SystemService {
private void makeVisible(IForensicServiceCommandCallback callback) throws RemoteException {
switch (mState) {
case STATE_INVISIBLE:
+ if (!mDataAggregator.initialize()) {
+ callback.onFailure(ERROR_DATA_SOURCE_UNAVAILABLE);
+ break;
+ }
mState = STATE_VISIBLE;
notifyStateMonitors();
callback.onSuccess();
@@ -227,6 +238,7 @@ public class ForensicService extends SystemService {
callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE);
break;
}
+ mDataAggregator.enable();
mState = STATE_ENABLED;
notifyStateMonitors();
callback.onSuccess();
@@ -243,6 +255,7 @@ public class ForensicService extends SystemService {
switch (mState) {
case STATE_ENABLED:
mBackupTransportConnection.release();
+ mDataAggregator.disable();
mState = STATE_VISIBLE;
notifyStateMonitors();
callback.onSuccess();
@@ -255,6 +268,17 @@ public class ForensicService extends SystemService {
}
}
+ /**
+ * Add a list of ForensicEvent.
+ */
+ public void addNewData(List<ForensicEvent> events) {
+ mHandler.obtainMessage(MSG_BACKUP, events).sendToTarget();
+ }
+
+ private void backup(List<ForensicEvent> events) {
+ mBackupTransportConnection.addData(events);
+ }
+
@Override
public void onStart() {
try {
@@ -275,6 +299,8 @@ public class ForensicService extends SystemService {
Looper getLooper();
BackupTransportConnection getBackupTransportConnection();
+
+ DataAggregator getDataAggregator(ForensicService forensicService);
}
private static final class InjectorImpl implements Injector {
@@ -303,6 +329,11 @@ public class ForensicService extends SystemService {
public BackupTransportConnection getBackupTransportConnection() {
return new BackupTransportConnection(mContext);
}
+
+ @Override
+ public DataAggregator getDataAggregator(ForensicService forensicService) {
+ return new DataAggregator(forensicService);
+ }
}
}
diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
index 2b55303bd89f..feb00e760ce6 100644
--- a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
+++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
@@ -19,23 +19,35 @@ package com.android.server.security.forensic;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.security.forensic.ForensicEvent;
import android.security.forensic.IForensicServiceCommandCallback;
import android.security.forensic.IForensicServiceStateCallback;
+import android.util.ArrayMap;
+
+import com.android.server.ServiceThread;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
public class ForensicServiceTest {
private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
@@ -55,10 +67,12 @@ public class ForensicServiceTest {
@Mock
private Context mContext;
private BackupTransportConnection mBackupTransportConnection;
-
+ private DataAggregator mDataAggregator;
private ForensicService mForensicService;
private TestLooper mTestLooper;
private Looper mLooper;
+ private TestLooper mTestLooperOfDataAggregator;
+ private Looper mLooperOfDataAggregator;
@SuppressLint("VisibleForTests")
@Before
@@ -67,6 +81,8 @@ public class ForensicServiceTest {
mTestLooper = new TestLooper();
mLooper = mTestLooper.getLooper();
+ mTestLooperOfDataAggregator = new TestLooper();
+ mLooperOfDataAggregator = mTestLooperOfDataAggregator.getLooper();
mForensicService = new ForensicService(new MockInjector(mContext));
mForensicService.onStart();
}
@@ -121,6 +137,8 @@ public class ForensicServiceTest {
assertEquals(STATE_INVISIBLE, scb1.mState);
assertEquals(STATE_INVISIBLE, scb2.mState);
+ doReturn(true).when(mDataAggregator).initialize();
+
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().makeVisible(ccb);
mTestLooper.dispatchAll();
@@ -130,6 +148,29 @@ public class ForensicServiceTest {
}
@Test
+ public void testMakeVisible_FromInvisible_TwoMonitors_DataSourceUnavailable()
+ throws RemoteException {
+ mForensicService.setState(STATE_INVISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+
+ doReturn(false).when(mDataAggregator).initialize();
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeVisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertNotNull(ccb.mErrorCode);
+ assertEquals(ERROR_DATA_SOURCE_UNAVAILABLE, ccb.mErrorCode.intValue());
+ }
+
+ @Test
public void testMakeVisible_FromVisible_TwoMonitors() throws RemoteException {
mForensicService.setState(STATE_VISIBLE);
StateCallback scb1 = new StateCallback();
@@ -262,6 +303,8 @@ public class ForensicServiceTest {
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().enable(ccb);
mTestLooper.dispatchAll();
+
+ verify(mDataAggregator, times(1)).enable();
assertEquals(STATE_ENABLED, scb1.mState);
assertEquals(STATE_ENABLED, scb2.mState);
assertNull(ccb.mErrorCode);
@@ -361,14 +404,67 @@ public class ForensicServiceTest {
doNothing().when(mBackupTransportConnection).release();
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().disable(ccb);
mTestLooper.dispatchAll();
+ mTestLooperOfDataAggregator.dispatchAll();
+ // TODO: We can verify the data sources once we implement them.
+ verify(mockThread, times(1)).quitSafely();
assertEquals(STATE_VISIBLE, scb1.mState);
assertEquals(STATE_VISIBLE, scb2.mState);
assertNull(ccb.mErrorCode);
}
+ @Test
+ public void testDataAggregator_AddBatchData() {
+ mForensicService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
+ String eventOneType = "event_one_type";
+ String eventOneMapKey = "event_one_map_key";
+ String eventOneMapVal = "event_one_map_val";
+ Map<String, String> eventOneMap = new ArrayMap<String, String>();
+ eventOneMap.put(eventOneMapKey, eventOneMapVal);
+ ForensicEvent eventOne = new ForensicEvent(eventOneType, eventOneMap);
+
+ String eventTwoType = "event_two_type";
+ String eventTwoMapKey = "event_two_map_key";
+ String eventTwoMapVal = "event_two_map_val";
+ Map<String, String> eventTwoMap = new ArrayMap<String, String>();
+ eventTwoMap.put(eventTwoMapKey, eventTwoMapVal);
+ ForensicEvent eventTwo = new ForensicEvent(eventTwoType, eventTwoMap);
+
+ List<ForensicEvent> events = new ArrayList<>();
+ events.add(eventOne);
+ events.add(eventTwo);
+
+ doReturn(true).when(mBackupTransportConnection).addData(any());
+
+ mDataAggregator.addBatchData(events);
+ mTestLooperOfDataAggregator.dispatchAll();
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class);
+ verify(mBackupTransportConnection).addData(captor.capture());
+ List<ForensicEvent> receivedEvents = captor.getValue();
+ assertEquals(receivedEvents.size(), 2);
+
+ assertEquals(receivedEvents.getFirst().getType(), eventOneType);
+ assertEquals(receivedEvents.getFirst().getKeyValuePairs().size(), 1);
+ assertEquals(receivedEvents.getFirst().getKeyValuePairs().get(eventOneMapKey),
+ eventOneMapVal);
+
+ assertEquals(receivedEvents.getLast().getType(), eventTwoType);
+ assertEquals(receivedEvents.getLast().getKeyValuePairs().size(), 1);
+ assertEquals(receivedEvents.getLast().getKeyValuePairs().get(eventTwoMapKey),
+ eventTwoMapVal);
+
+ }
+
private class MockInjector implements ForensicService.Injector {
private final Context mContext;
@@ -393,6 +489,11 @@ public class ForensicServiceTest {
return mBackupTransportConnection;
}
+ @Override
+ public DataAggregator getDataAggregator(ForensicService forensicService) {
+ mDataAggregator = spy(new DataAggregator(forensicService));
+ return mDataAggregator;
+ }
}
private static class StateCallback extends IForensicServiceStateCallback.Stub {