Loading core/java/android/security/forensic/ForensicEvent.java +8 −0 Original line number Diff line number Diff line Loading @@ -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); Loading services/core/java/com/android/server/security/forensic/DataAggregator.java 0 → 100644 +142 −0 Original line number Diff line number Diff line /* * 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); } } } } services/core/java/com/android/server/security/forensic/DataSource.java 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * 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(); } services/core/java/com/android/server/security/forensic/ForensicService.java +31 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -32,6 +33,7 @@ import com.android.server.ServiceThread; import com.android.server.SystemService; import java.util.ArrayList; import java.util.List; /** * @hide Loading Loading @@ -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<>(); Loading @@ -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); } Loading Loading @@ -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); } Loading @@ -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(); Loading Loading @@ -227,6 +238,7 @@ public class ForensicService extends SystemService { callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE); break; } mDataAggregator.enable(); mState = STATE_ENABLED; notifyStateMonitors(); callback.onSuccess(); Loading @@ -243,6 +255,7 @@ public class ForensicService extends SystemService { switch (mState) { case STATE_ENABLED: mBackupTransportConnection.release(); mDataAggregator.disable(); mState = STATE_VISIBLE; notifyStateMonitors(); callback.onSuccess(); Loading @@ -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 { Loading @@ -275,6 +299,8 @@ public class ForensicService extends SystemService { Looper getLooper(); BackupTransportConnection getBackupTransportConnection(); DataAggregator getDataAggregator(ForensicService forensicService); } private static final class InjectorImpl implements Injector { Loading Loading @@ -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); } } } services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java +102 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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(); } Loading Loading @@ -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(); Loading @@ -129,6 +147,29 @@ public class ForensicServiceTest { assertNull(ccb.mErrorCode); } @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); Loading Loading @@ -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); Loading Loading @@ -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; Loading @@ -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 { Loading Loading
core/java/android/security/forensic/ForensicEvent.java +8 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
services/core/java/com/android/server/security/forensic/DataAggregator.java 0 → 100644 +142 −0 Original line number Diff line number Diff line /* * 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); } } } }
services/core/java/com/android/server/security/forensic/DataSource.java 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * 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(); }
services/core/java/com/android/server/security/forensic/ForensicService.java +31 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -32,6 +33,7 @@ import com.android.server.ServiceThread; import com.android.server.SystemService; import java.util.ArrayList; import java.util.List; /** * @hide Loading Loading @@ -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<>(); Loading @@ -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); } Loading Loading @@ -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); } Loading @@ -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(); Loading Loading @@ -227,6 +238,7 @@ public class ForensicService extends SystemService { callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE); break; } mDataAggregator.enable(); mState = STATE_ENABLED; notifyStateMonitors(); callback.onSuccess(); Loading @@ -243,6 +255,7 @@ public class ForensicService extends SystemService { switch (mState) { case STATE_ENABLED: mBackupTransportConnection.release(); mDataAggregator.disable(); mState = STATE_VISIBLE; notifyStateMonitors(); callback.onSuccess(); Loading @@ -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 { Loading @@ -275,6 +299,8 @@ public class ForensicService extends SystemService { Looper getLooper(); BackupTransportConnection getBackupTransportConnection(); DataAggregator getDataAggregator(ForensicService forensicService); } private static final class InjectorImpl implements Injector { Loading Loading @@ -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); } } }
services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java +102 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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(); } Loading Loading @@ -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(); Loading @@ -129,6 +147,29 @@ public class ForensicServiceTest { assertNull(ccb.mErrorCode); } @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); Loading Loading @@ -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); Loading Loading @@ -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; Loading @@ -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 { Loading