diff options
| author | 2024-10-26 10:00:03 -0700 | |
|---|---|---|
| committer | 2024-11-13 14:49:25 -0800 | |
| commit | 18b98714e554bea6e7acadf2285fb80f1ce2b424 (patch) | |
| tree | 867296586c77017a3e5785ef7c8d87c317cec44d | |
| parent | 2c44495acb9dfe5ce0ca35c9d6870ceca4abd4a4 (diff) | |
[Forensic] Add ForensicManager and permissions
The ForensicManager serves as client lib to communicate with the
ForensicService.
The following 3 permissions are introduced to guard the ForensicService:
READ_FORENSIC_STATE: Allows an application to monitor the state
of the ForensicService.
MANAGE_FORENSIC_STATE: Allows an application to change the state
of the ForensicService.
BIND_FORENSIC_BACKUP_SERVICE:Must be required by any
ForensicBackupService to ensure that only the system can bind to it.
Bug: 365994454
Test: atest ForensicServiceTest ForensicManagerTest
Flag: android.security.afl_api
Ignore-AOSP-First: security feature
Change-Id: Icc196812fd85da1c3f0b7860aab7b3c2e08e9046
16 files changed, 626 insertions, 370 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ed95fdd52f45..b7d9fdd4435c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -65,6 +65,7 @@ package android { field @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public static final String BIND_EXPLICIT_HEALTH_CHECK_SERVICE = "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"; field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE"; field public static final String BIND_FIELD_CLASSIFICATION_SERVICE = "android.permission.BIND_FIELD_CLASSIFICATION_SERVICE"; + field @FlaggedApi("android.security.afl_api") public static final String BIND_FORENSIC_EVENT_TRANSPORT_SERVICE = "android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE"; field public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE"; field public static final String BIND_HOTWORD_DETECTION_SERVICE = "android.permission.BIND_HOTWORD_DETECTION_SERVICE"; field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE"; @@ -209,6 +210,7 @@ package android { field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"; field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; + field @FlaggedApi("android.security.afl_api") public static final String MANAGE_FORENSIC_STATE = "android.permission.MANAGE_FORENSIC_STATE"; field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY"; field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE"; field @FlaggedApi("android.media.tv.flags.media_quality_fw") public static final String MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE = "android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"; @@ -304,6 +306,7 @@ package android { field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS"; field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG"; field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE"; + field @FlaggedApi("android.security.afl_api") public static final String READ_FORENSIC_STATE = "android.permission.READ_FORENSIC_STATE"; field public static final String READ_GLOBAL_APP_SEARCH_DATA = "android.permission.READ_GLOBAL_APP_SEARCH_DATA"; field @FlaggedApi("android.content.pm.get_resolved_apk_path") public static final String READ_INSTALLED_SESSION_PATHS = "android.permission.READ_INSTALLED_SESSION_PATHS"; field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS"; @@ -12570,6 +12573,29 @@ package android.security.advancedprotection { } +package android.security.forensic { + + @FlaggedApi("android.security.afl_api") public class ForensicManager { + method @RequiresPermission(android.Manifest.permission.READ_FORENSIC_STATE) public void addStateCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.MANAGE_FORENSIC_STATE) public void disable(@NonNull java.util.concurrent.Executor, @NonNull android.security.forensic.ForensicManager.CommandCallback); + method @RequiresPermission(android.Manifest.permission.MANAGE_FORENSIC_STATE) public void enable(@NonNull java.util.concurrent.Executor, @NonNull android.security.forensic.ForensicManager.CommandCallback); + method @RequiresPermission(android.Manifest.permission.READ_FORENSIC_STATE) public void removeStateCallback(@NonNull java.util.function.Consumer<java.lang.Integer>); + field public static final int ERROR_DATA_SOURCE_UNAVAILABLE = 4; // 0x4 + field public static final int ERROR_PERMISSION_DENIED = 1; // 0x1 + field public static final int ERROR_TRANSPORT_UNAVAILABLE = 3; // 0x3 + field public static final int ERROR_UNKNOWN = 0; // 0x0 + field public static final int STATE_DISABLED = 1; // 0x1 + field public static final int STATE_ENABLED = 2; // 0x2 + field public static final int STATE_UNKNOWN = 0; // 0x0 + } + + public static interface ForensicManager.CommandCallback { + method public void onFailure(int); + method public void onSuccess(); + } + +} + package android.security.keystore { public class AndroidKeyStoreProvider extends java.security.Provider { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e451116081fa..f6ae8a962077 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -238,6 +238,8 @@ import android.security.advancedprotection.AdvancedProtectionManager; import android.security.advancedprotection.IAdvancedProtectionService; import android.security.attestationverification.AttestationVerificationManager; import android.security.attestationverification.IAttestationVerificationManagerService; +import android.security.forensic.ForensicManager; +import android.security.forensic.IForensicService; import android.security.keystore.KeyStoreManager; import android.service.oemlock.IOemLockService; import android.service.oemlock.OemLockManager; @@ -1790,6 +1792,18 @@ public final class SystemServiceRegistry { } }); + registerService(Context.FORENSIC_SERVICE, ForensicManager.class, + new CachedServiceFetcher<ForensicManager>() { + @Override + public ForensicManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.FORENSIC_SERVICE); + IForensicService service = IForensicService.Stub.asInterface(b); + return new ForensicManager(service); + } + }); + sInitializing = true; try { // Note: the following functions need to be @SystemApis, once they become mainline diff --git a/core/java/android/security/forensic/ForensicManager.java b/core/java/android/security/forensic/ForensicManager.java new file mode 100644 index 000000000000..9126182eda7b --- /dev/null +++ b/core/java/android/security/forensic/ForensicManager.java @@ -0,0 +1,276 @@ +/* + * 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 android.security.forensic; + +import static android.Manifest.permission.MANAGE_FORENSIC_STATE; +import static android.Manifest.permission.READ_FORENSIC_STATE; + +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.security.Flags; +import android.util.Log; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * ForensicManager manages the forensic logging on Android devices. + * Upon user consent, forensic logging collects various device events for + * off-device investigation of potential device compromise. + * <p> + * Forensic logging can either be enabled ({@link #STATE_ENABLED} + * or disabled ({@link #STATE_DISABLED}). + * <p> + * The Forensic logs will be transferred to + * {@link android.security.forensic.ForensicEventTransport}. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_AFL_API) +@SystemService(Context.FORENSIC_SERVICE) +public class ForensicManager { + private static final String TAG = "ForensicManager"; + + /** @hide */ + @Target(ElementType.TYPE_USE) + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "STATE_" }, value = { + STATE_UNKNOWN, + STATE_DISABLED, + STATE_ENABLED + }) + public @interface ForensicState {} + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_UNKNOWN, + ERROR_PERMISSION_DENIED, + ERROR_TRANSPORT_UNAVAILABLE, + ERROR_DATA_SOURCE_UNAVAILABLE + }) + public @interface ForensicError {} + + /** + * Indicates an unknown state + */ + public static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN; + + /** + * Indicates an state that the forensic is turned off. + */ + public static final int STATE_DISABLED = IForensicServiceStateCallback.State.DISABLED; + + /** + * Indicates an state that the forensic is turned on. + */ + public static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED; + + /** + * Indicates an unknown error + */ + public static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN; + + /** + * Indicates an error due to insufficient access rights. + */ + public static final int ERROR_PERMISSION_DENIED = + IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED; + + /** + * Indicates an error due to unavailability of the forensic event transport. + */ + public static final int ERROR_TRANSPORT_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE; + + /** + * Indicates an error due to unavailability of the data source. + */ + public static final int ERROR_DATA_SOURCE_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE; + + + private final IForensicService mService; + + private final ConcurrentHashMap<Consumer<Integer>, IForensicServiceStateCallback> + mStateCallbacks = new ConcurrentHashMap<>(); + + /** + * Constructor + * + * @param service A valid instance of IForensicService. + * @hide + */ + public ForensicManager(IForensicService service) { + mService = service; + } + + /** + * Add a callback to monitor the state of the ForensicService. + * + * @param executor The executor through which the callback should be invoked. + * @param callback The callback for state change. + * Once the callback is registered, the callback will be called + * to reflect the init state. + * The callback can be registered only once. + */ + @RequiresPermission(READ_FORENSIC_STATE) + public void addStateCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull @ForensicState Consumer<Integer> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + if (mStateCallbacks.get(callback) != null) { + Log.d(TAG, "addStateCallback callback already present"); + return; + } + + final IForensicServiceStateCallback wrappedCallback = + new IForensicServiceStateCallback.Stub() { + @Override + public void onStateChange(int state) { + executor.execute(() -> callback.accept(state)); + } + }; + try { + mService.addStateCallback(wrappedCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + mStateCallbacks.put(callback, wrappedCallback); + } + + /** + * Remove a callback to monitor the state of the ForensicService. + * + * @param callback The callback to remove. + */ + @RequiresPermission(READ_FORENSIC_STATE) + public void removeStateCallback(@NonNull Consumer<@ForensicState Integer> callback) { + Objects.requireNonNull(callback); + if (!mStateCallbacks.containsKey(callback)) { + Log.d(TAG, "removeStateCallback callback not present"); + return; + } + + IForensicServiceStateCallback wrappedCallback = mStateCallbacks.get(callback); + + try { + mService.removeStateCallback(wrappedCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + mStateCallbacks.remove(callback); + } + + /** + * Enable forensic logging. + * If successful, ForensicService will transition to {@link #STATE_ENABLED} state. + * <p> + * When forensic logging is enabled, various device events will be collected and + * sent over to the registered {@link android.security.forensic.ForensicEventTransport}. + * + * @param executor The executor through which the callback should be invoked. + * @param callback The callback for the command result. + */ + @RequiresPermission(MANAGE_FORENSIC_STATE) + public void enable(@NonNull @CallbackExecutor Executor executor, + @NonNull CommandCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + try { + mService.enable(new IForensicServiceCommandCallback.Stub() { + @Override + public void onSuccess() { + executor.execute(callback::onSuccess); + } + + @Override + public void onFailure(int error) { + executor.execute(() -> callback.onFailure(error)); + } + }); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Disable forensic logging. + * If successful, ForensicService will transition to {@link #STATE_DISABLED}. + * <p> + * When forensic logging is disabled, device events will no longer be collected. + * Any events that have been collected but not yet sent to ForensicEventTransport + * will be transferred as a final batch. + * + * @param executor The executor through which the callback should be invoked. + * @param callback The callback for the command result. + */ + @RequiresPermission(MANAGE_FORENSIC_STATE) + public void disable(@NonNull @CallbackExecutor Executor executor, + @NonNull CommandCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + try { + mService.disable(new IForensicServiceCommandCallback.Stub() { + @Override + public void onSuccess() { + executor.execute(callback::onSuccess); + } + + @Override + public void onFailure(int error) { + executor.execute(() -> callback.onFailure(error)); + } + }); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Callback used in {@link #enable} and {@link #disable} to indicate the result of the command. + */ + public interface CommandCallback { + /** + * Called when command succeeds. + */ + void onSuccess(); + + /** + * Called when command fails. + * @param error The error number. + */ + void onFailure(@ForensicError int error); + } +} diff --git a/core/java/android/security/forensic/IBackupTransport.aidl b/core/java/android/security/forensic/IForensicEventTransport.aidl index c2cbc83ba1b3..80e78eb9cf49 100644 --- a/core/java/android/security/forensic/IBackupTransport.aidl +++ b/core/java/android/security/forensic/IForensicEventTransport.aidl @@ -20,7 +20,7 @@ import android.security.forensic.ForensicEvent; import com.android.internal.infra.AndroidFuture; /** {@hide} */ -oneway interface IBackupTransport { +oneway interface IForensicEventTransport { /** * Initialize the server side. */ diff --git a/core/java/android/security/forensic/IForensicService.aidl b/core/java/android/security/forensic/IForensicService.aidl index a944b18cb26d..8039b264f0e5 100644 --- a/core/java/android/security/forensic/IForensicService.aidl +++ b/core/java/android/security/forensic/IForensicService.aidl @@ -24,9 +24,12 @@ import android.security.forensic.IForensicServiceStateCallback; * @hide */ interface IForensicService { - void monitorState(IForensicServiceStateCallback callback); - void makeVisible(IForensicServiceCommandCallback callback); - void makeInvisible(IForensicServiceCommandCallback callback); + @EnforcePermission("READ_FORENSIC_STATE") + void addStateCallback(IForensicServiceStateCallback callback); + @EnforcePermission("READ_FORENSIC_STATE") + void removeStateCallback(IForensicServiceStateCallback callback); + @EnforcePermission("MANAGE_FORENSIC_STATE") void enable(IForensicServiceCommandCallback callback); + @EnforcePermission("MANAGE_FORENSIC_STATE") void disable(IForensicServiceCommandCallback callback); } diff --git a/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl index 7fa0c7f72690..6d1456ea0426 100644 --- a/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl +++ b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl @@ -25,8 +25,8 @@ package android.security.forensic; UNKNOWN = 0, PERMISSION_DENIED = 1, INVALID_STATE_TRANSITION = 2, - BACKUP_TRANSPORT_UNAVAILABLE = 3, - DATA_SOURCE_UNAVAILABLE = 3, + TRANSPORT_UNAVAILABLE = 3, + DATA_SOURCE_UNAVAILABLE = 4, } void onSuccess(); void onFailure(ErrorCode error); diff --git a/core/java/android/security/forensic/IForensicServiceStateCallback.aidl b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl index 0cda35083ffd..1b68c7b14bca 100644 --- a/core/java/android/security/forensic/IForensicServiceStateCallback.aidl +++ b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl @@ -23,9 +23,8 @@ package android.security.forensic; @Backing(type="int") enum State{ UNKNOWN = 0, - INVISIBLE = 1, - VISIBLE = 2, - ENABLED = 3, + DISABLED = 1, + ENABLED = 2, } void onStateChange(State state); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3e0c1200749e..eb2633178c43 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4144,6 +4144,37 @@ <uses-permission android:name="android.permission.QUERY_ADVANCED_PROTECTION_MODE" android:featureFlag="android.security.aapm_api"/> + <!-- Allows an application to read the state of the ForensicService + @FlaggedApi(android.security.Flags.FLAG_AFL_API) + @SystemApi + @hide --> + <permission android:name="android.permission.READ_FORENSIC_STATE" + android:featureFlag="android.security.afl_api" + android:protectionLevel="signature|privileged" /> + <uses-permission android:name="android.permission.READ_FORENSIC_STATE" + android:featureFlag="android.security.afl_api"/> + + <!-- Allows an application to change the state of the ForensicService + @FlaggedApi(android.security.Flags.FLAG_AFL_API) + @SystemApi + @hide --> + <permission android:name="android.permission.MANAGE_FORENSIC_STATE" + android:featureFlag="android.security.afl_api" + android:protectionLevel="signature|privileged" /> + <uses-permission android:name="android.permission.MANAGE_FORENSIC_STATE" + android:featureFlag="android.security.afl_api"/> + + <!-- Must be required by any ForensicEventTransportService to ensure that + only the system can bind to it. + @FlaggedApi(android.security.Flags.FLAG_AFL_API) + @SystemApi + @hide --> + <permission android:name="android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE" + android:featureFlag="android.security.afl_api" + android:protectionLevel="signature" /> + <uses-permission android:name="android.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE" + android:featureFlag="android.security.afl_api"/> + <!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.--> <permission android:name="android.permission.PROVISION_DEMO_DEVICE" android:protectionLevel="signature|setup|knownSigner" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 969ee2e16deb..32877251823d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -7212,8 +7212,8 @@ <!-- Package for opening identity check settings page [CHAR LIMIT=NONE] [DO NOT TRANSLATE] --> <string name="identity_check_settings_package_name">com\u002eandroid\u002esettings</string> - <!-- The name of the service for forensic backup transport. --> - <string name="config_forensicBackupTransport" translatable="false"></string> + <!-- The name of the service for forensic event transport. --> + <string name="config_forensicEventTransport" translatable="false"></string> <!-- Whether to enable fp unlock when screen turns off on udfps devices --> <bool name="config_screen_off_udfps_enabled">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9dd302784c2c..7fe09128de05 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5679,8 +5679,8 @@ <java-symbol type="string" name="identity_check_settings_action" /> <java-symbol type="string" name="identity_check_settings_package_name" /> - <!-- Forensic backup transport --> - <java-symbol type="string" name="config_forensicBackupTransport" /> + <!-- Forensic event transport --> + <java-symbol type="string" name="config_forensicEventTransport" /> <!-- Fingerprint screen off unlock config --> <java-symbol type="bool" name="config_screen_off_udfps_enabled" /> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 541ca602a386..74235672ad3f 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -597,6 +597,9 @@ applications that come with the platform <!-- Permissions required for CTS test - SettingsPreferenceServiceClientTest --> <permission name="android.permission.READ_SYSTEM_PREFERENCES" /> <permission name="android.permission.WRITE_SYSTEM_PREFERENCES" /> + <!-- Permission required for CTS test - ForensicManagerTest --> + <permission name="android.permission.READ_FORENSIC_STATE" /> + <permission name="android.permission.MANAGE_FORENSIC_STATE" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 7b6321d1cc7d..859445eb9dd0 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -953,6 +953,13 @@ <uses-permission android:name="android.permission.QUERY_ADVANCED_PROTECTION_MODE" android:featureFlag="android.security.aapm_api"/> + <!-- Permission required for CTS test - ForensicManagerTest --> + <uses-permission android:name="android.permission.READ_FORENSIC_STATE" + android:featureFlag="android.security.afl_api"/> + <uses-permission android:name="android.permission.MANAGE_FORENSIC_STATE" + android:featureFlag="android.security.afl_api"/> + + <!-- Permission required for CTS test - CtsAppTestCases --> <uses-permission android:name="android.permission.KILL_UID" /> diff --git a/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java b/services/core/java/com/android/server/security/forensic/ForensicEventTransportConnection.java index caca011b6549..b85199ed9218 100644 --- a/services/core/java/com/android/server/security/forensic/BackupTransportConnection.java +++ b/services/core/java/com/android/server/security/forensic/ForensicEventTransportConnection.java @@ -16,15 +16,19 @@ package com.android.server.security.forensic; +import static android.Manifest.permission.BIND_FORENSIC_EVENT_TRANSPORT_SERVICE; + import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.security.forensic.ForensicEvent; -import android.security.forensic.IBackupTransport; +import android.security.forensic.IForensicEventTransport; import android.text.TextUtils; import android.util.Slog; @@ -36,20 +40,20 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -public class BackupTransportConnection implements ServiceConnection { - private static final String TAG = "BackupTransportConnection"; +public class ForensicEventTransportConnection implements ServiceConnection { + private static final String TAG = "ForensicEventTransportConnection"; private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins private final Context mContext; - private String mForensicBackupTransportConfig; - volatile IBackupTransport mService; + private String mForensicEventTransportConfig; + volatile IForensicEventTransport mService; - public BackupTransportConnection(Context context) { + public ForensicEventTransportConnection(Context context) { mContext = context; mService = null; } /** - * Initialize the BackupTransport binder service. + * Initialize the ForensicEventTransport binder service. * @return Whether the initialization succeed. */ public boolean initialize() { @@ -74,7 +78,7 @@ public class BackupTransportConnection implements ServiceConnection { } /** - * Add data to the BackupTransport binder service. + * Add data to the ForensicEventTransport binder service. * @param data List of ForensicEvent. * @return Whether the data is added to the binder service. */ @@ -109,21 +113,37 @@ public class BackupTransportConnection implements ServiceConnection { return future.get(FUTURE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException | TimeoutException | CancellationException e) { - Slog.w(TAG, "Failed to get result from transport:", e); + Slog.e(TAG, "Failed to get result from transport:", e); return null; } } private boolean bindService() { - mForensicBackupTransportConfig = mContext.getString( - com.android.internal.R.string.config_forensicBackupTransport); - if (TextUtils.isEmpty(mForensicBackupTransportConfig)) { + mForensicEventTransportConfig = mContext.getString( + com.android.internal.R.string.config_forensicEventTransport); + if (TextUtils.isEmpty(mForensicEventTransportConfig)) { + Slog.e(TAG, "config_forensicEventTransport is empty"); return false; } ComponentName serviceComponent = - ComponentName.unflattenFromString(mForensicBackupTransportConfig); + ComponentName.unflattenFromString(mForensicEventTransportConfig); if (serviceComponent == null) { + Slog.e(TAG, "Can't get serviceComponent name"); + return false; + } + + try { + ServiceInfo serviceInfo = mContext.getPackageManager().getServiceInfo(serviceComponent, + 0 /* flags */); + if (!BIND_FORENSIC_EVENT_TRANSPORT_SERVICE.equals(serviceInfo.permission)) { + Slog.e(TAG, serviceComponent.flattenToShortString() + + " is not declared with the permission " + + "\"" + BIND_FORENSIC_EVENT_TRANSPORT_SERVICE + "\""); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Unable to find serviceComponent"); return false; } @@ -143,7 +163,7 @@ public class BackupTransportConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { - mService = IBackupTransport.Stub.asInterface(service); + mService = IForensicEventTransport.Stub.asInterface(service); } @Override 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 01f630b60ff5..2be068fa2f83 100644 --- a/services/core/java/com/android/server/security/forensic/ForensicService.java +++ b/services/core/java/com/android/server/security/forensic/ForensicService.java @@ -16,11 +16,16 @@ package com.android.server.security.forensic; +import static android.Manifest.permission.MANAGE_FORENSIC_STATE; +import static android.Manifest.permission.READ_FORENSIC_STATE; + +import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.PermissionEnforcer; import android.os.RemoteException; import android.security.forensic.ForensicEvent; import android.security.forensic.IForensicService; @@ -41,16 +46,15 @@ import java.util.List; public class ForensicService extends SystemService { private static final String TAG = "ForensicService"; - private static final int MSG_MONITOR_STATE = 0; - private static final int MSG_MAKE_VISIBLE = 1; - private static final int MSG_MAKE_INVISIBLE = 2; - private static final int MSG_ENABLE = 3; - private static final int MSG_DISABLE = 4; - private static final int MSG_BACKUP = 5; + private static final int MAX_STATE_CALLBACK_NUM = 16; + private static final int MSG_ADD_STATE_CALLBACK = 0; + private static final int MSG_REMOVE_STATE_CALLBACK = 1; + private static final int MSG_ENABLE = 2; + private static final int MSG_DISABLE = 3; + private static final int MSG_TRANSPORT = 4; private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN; - private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE; - private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE; + private static final int STATE_DISABLED = IForensicServiceStateCallback.State.DISABLED; private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED; private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN; @@ -58,19 +62,19 @@ public class ForensicService extends SystemService { IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED; private static final int ERROR_INVALID_STATE_TRANSITION = IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION; - private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE = - IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE; + private static final int ERROR_TRANSPORT_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE; private static final int ERROR_DATA_SOURCE_UNAVAILABLE = IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE; private final Context mContext; private final Handler mHandler; - private final BackupTransportConnection mBackupTransportConnection; + private final ForensicEventTransportConnection mForensicEventTransportConnection; private final DataAggregator mDataAggregator; private final BinderService mBinderService; - private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>(); - private volatile int mState = STATE_INVISIBLE; + private final ArrayList<IForensicServiceStateCallback> mStateCallbacks = new ArrayList<>(); + private volatile int mState = STATE_DISABLED; public ForensicService(@NonNull Context context) { this(new InjectorImpl(context)); @@ -81,9 +85,9 @@ public class ForensicService extends SystemService { super(injector.getContext()); mContext = injector.getContext(); mHandler = new EventHandler(injector.getLooper(), this); - mBackupTransportConnection = injector.getBackupTransportConnection(); + mForensicEventTransportConnection = injector.getForensicEventransportConnection(); mDataAggregator = injector.getDataAggregator(this); - mBinderService = new BinderService(this); + mBinderService = new BinderService(this, injector.getPermissionEnforcer()); } @VisibleForTesting @@ -94,32 +98,36 @@ public class ForensicService extends SystemService { private static final class BinderService extends IForensicService.Stub { final ForensicService mService; - BinderService(ForensicService service) { + BinderService(ForensicService service, @NonNull PermissionEnforcer permissionEnforcer) { + super(permissionEnforcer); mService = service; } @Override - public void monitorState(IForensicServiceStateCallback callback) { - mService.mHandler.obtainMessage(MSG_MONITOR_STATE, callback).sendToTarget(); - } - - @Override - public void makeVisible(IForensicServiceCommandCallback callback) { - mService.mHandler.obtainMessage(MSG_MAKE_VISIBLE, callback).sendToTarget(); + @EnforcePermission(READ_FORENSIC_STATE) + public void addStateCallback(IForensicServiceStateCallback callback) { + addStateCallback_enforcePermission(); + mService.mHandler.obtainMessage(MSG_ADD_STATE_CALLBACK, callback).sendToTarget(); } @Override - public void makeInvisible(IForensicServiceCommandCallback callback) { - mService.mHandler.obtainMessage(MSG_MAKE_INVISIBLE, callback).sendToTarget(); + @EnforcePermission(READ_FORENSIC_STATE) + public void removeStateCallback(IForensicServiceStateCallback callback) { + removeStateCallback_enforcePermission(); + mService.mHandler.obtainMessage(MSG_REMOVE_STATE_CALLBACK, callback).sendToTarget(); } @Override + @EnforcePermission(MANAGE_FORENSIC_STATE) public void enable(IForensicServiceCommandCallback callback) { + enable_enforcePermission(); mService.mHandler.obtainMessage(MSG_ENABLE, callback).sendToTarget(); } @Override + @EnforcePermission(MANAGE_FORENSIC_STATE) public void disable(IForensicServiceCommandCallback callback) { + disable_enforcePermission(); mService.mHandler.obtainMessage(MSG_DISABLE, callback).sendToTarget(); } } @@ -135,24 +143,18 @@ public class ForensicService extends SystemService { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_MONITOR_STATE: + case MSG_ADD_STATE_CALLBACK: try { - mService.monitorState( + mService.addStateCallback( (IForensicServiceStateCallback) msg.obj); } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } break; - case MSG_MAKE_VISIBLE: + case MSG_REMOVE_STATE_CALLBACK: try { - mService.makeVisible((IForensicServiceCommandCallback) msg.obj); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - break; - case MSG_MAKE_INVISIBLE: - try { - mService.makeInvisible((IForensicServiceCommandCallback) msg.obj); + mService.removeStateCallback( + (IForensicServiceStateCallback) msg.obj); } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } @@ -171,8 +173,8 @@ public class ForensicService extends SystemService { Slog.e(TAG, "RemoteException", e); } break; - case MSG_BACKUP: - mService.backup((List<ForensicEvent>) msg.obj); + case MSG_TRANSPORT: + mService.transport((List<ForensicEvent>) msg.obj); break; default: Slog.w(TAG, "Unknown message: " + msg.what); @@ -180,103 +182,83 @@ public class ForensicService extends SystemService { } } - private void monitorState(IForensicServiceStateCallback callback) throws RemoteException { - for (int i = 0; i < mStateMonitors.size(); i++) { - if (mStateMonitors.get(i).asBinder() == callback.asBinder()) { + private void addStateCallback(IForensicServiceStateCallback callback) throws RemoteException { + for (int i = 0; i < mStateCallbacks.size(); i++) { + if (mStateCallbacks.get(i).asBinder() == callback.asBinder()) { return; } } - mStateMonitors.add(callback); + mStateCallbacks.add(callback); callback.onStateChange(mState); } - private void notifyStateMonitors() throws RemoteException { - for (int i = 0; i < mStateMonitors.size(); i++) { - mStateMonitors.get(i).onStateChange(mState); + private void removeStateCallback(IForensicServiceStateCallback callback) + throws RemoteException { + for (int i = 0; i < mStateCallbacks.size(); i++) { + if (mStateCallbacks.get(i).asBinder() == callback.asBinder()) { + mStateCallbacks.remove(i); + return; + } } } - 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(); - break; - case STATE_VISIBLE: - callback.onSuccess(); - break; - default: - callback.onFailure(ERROR_INVALID_STATE_TRANSITION); + private void notifyStateMonitors() { + if (mStateCallbacks.size() >= MAX_STATE_CALLBACK_NUM) { + mStateCallbacks.removeFirst(); } - } - private void makeInvisible(IForensicServiceCommandCallback callback) throws RemoteException { - switch (mState) { - case STATE_VISIBLE: - case STATE_ENABLED: - mState = STATE_INVISIBLE; - notifyStateMonitors(); - callback.onSuccess(); - break; - case STATE_INVISIBLE: - callback.onSuccess(); - break; - default: - callback.onFailure(ERROR_INVALID_STATE_TRANSITION); + for (int i = 0; i < mStateCallbacks.size(); i++) { + try { + mStateCallbacks.get(i).onStateChange(mState); + } catch (RemoteException e) { + mStateCallbacks.remove(i); + } } } private void enable(IForensicServiceCommandCallback callback) throws RemoteException { - switch (mState) { - case STATE_VISIBLE: - if (!mBackupTransportConnection.initialize()) { - callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE); - break; - } - mDataAggregator.enable(); - mState = STATE_ENABLED; - notifyStateMonitors(); - callback.onSuccess(); - break; - case STATE_ENABLED: - callback.onSuccess(); - break; - default: - callback.onFailure(ERROR_INVALID_STATE_TRANSITION); + if (mState == STATE_ENABLED) { + callback.onSuccess(); + return; } + + // TODO: temporarily disable the following for the CTS ForensicManagerTest. + // Enable it when the transport component is ready. + // if (!mForensicEventTransportConnection.initialize()) { + // callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE); + // return; + // } + + mDataAggregator.enable(); + mState = STATE_ENABLED; + notifyStateMonitors(); + callback.onSuccess(); } private void disable(IForensicServiceCommandCallback callback) throws RemoteException { - switch (mState) { - case STATE_ENABLED: - mBackupTransportConnection.release(); - mDataAggregator.disable(); - mState = STATE_VISIBLE; - notifyStateMonitors(); - callback.onSuccess(); - break; - case STATE_VISIBLE: - callback.onSuccess(); - break; - default: - callback.onFailure(ERROR_INVALID_STATE_TRANSITION); + if (mState == STATE_DISABLED) { + callback.onSuccess(); + return; } + + // TODO: temporarily disable the following for the CTS ForensicManagerTest. + // Enable it when the transport component is ready. + // mForensicEventTransportConnection.release(); + mDataAggregator.disable(); + mState = STATE_DISABLED; + notifyStateMonitors(); + callback.onSuccess(); } /** * Add a list of ForensicEvent. */ public void addNewData(List<ForensicEvent> events) { - mHandler.obtainMessage(MSG_BACKUP, events).sendToTarget(); + mHandler.obtainMessage(MSG_TRANSPORT, events).sendToTarget(); } - private void backup(List<ForensicEvent> events) { - mBackupTransportConnection.addData(events); + private void transport(List<ForensicEvent> events) { + mForensicEventTransportConnection.addData(events); } @Override @@ -296,9 +278,11 @@ public class ForensicService extends SystemService { interface Injector { Context getContext(); + PermissionEnforcer getPermissionEnforcer(); + Looper getLooper(); - BackupTransportConnection getBackupTransportConnection(); + ForensicEventTransportConnection getForensicEventransportConnection(); DataAggregator getDataAggregator(ForensicService forensicService); } @@ -315,6 +299,10 @@ public class ForensicService extends SystemService { return mContext; } + @Override + public PermissionEnforcer getPermissionEnforcer() { + return PermissionEnforcer.fromContext(mContext); + } @Override public Looper getLooper() { @@ -326,8 +314,8 @@ public class ForensicService extends SystemService { } @Override - public BackupTransportConnection getBackupTransportConnection() { - return new BackupTransportConnection(mContext); + public ForensicEventTransportConnection getForensicEventransportConnection() { + return new ForensicEventTransportConnection(mContext); } @Override diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d0bf02dab60c..3e7c4ef4019f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -251,6 +251,7 @@ import com.android.server.security.KeyAttestationApplicationIdProviderService; import com.android.server.security.KeyChainSystemService; import com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService; import com.android.server.security.advancedprotection.AdvancedProtectionService; +import com.android.server.security.forensic.ForensicService; import com.android.server.security.rkp.RemoteProvisioningService; import com.android.server.selinux.SelinuxAuditLogsService; import com.android.server.sensorprivacy.SensorPrivacyService; @@ -1760,6 +1761,13 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(LogcatManagerService.class); t.traceEnd(); + if (!isWatch && !isTv && !isAutomotive + && android.security.Flags.aflApi()) { + t.traceBegin("StartForensicService"); + mSystemServiceManager.startService(ForensicService.class); + t.traceEnd(); + } + if (AppFunctionManagerConfiguration.isSupported(context)) { t.traceBegin("StartAppFunctionManager"); mSystemServiceManager.startService(AppFunctionManagerService.class); 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 40e00344f87a..0da6db634330 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 @@ -16,9 +16,13 @@ package com.android.server.security.forensic; +import static android.Manifest.permission.MANAGE_FORENSIC_STATE; +import static android.Manifest.permission.READ_FORENSIC_STATE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -29,7 +33,9 @@ import static org.mockito.Mockito.verify; import android.annotation.SuppressLint; import android.content.Context; import android.os.Looper; +import android.os.PermissionEnforcer; import android.os.RemoteException; +import android.os.test.FakePermissionEnforcer; import android.os.test.TestLooper; import android.security.forensic.ForensicEvent; import android.security.forensic.IForensicServiceCommandCallback; @@ -41,6 +47,7 @@ import androidx.test.core.app.ApplicationProvider; import com.android.server.ServiceThread; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -50,34 +57,36 @@ 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; - private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE; + private static final int STATE_DISABLED = IForensicServiceStateCallback.State.DISABLED; private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED; private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN; private static final int ERROR_PERMISSION_DENIED = IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED; - private static final int ERROR_INVALID_STATE_TRANSITION = - IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION; - private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE = - IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE; + private static final int ERROR_TRANSPORT_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.TRANSPORT_UNAVAILABLE; private static final int ERROR_DATA_SOURCE_UNAVAILABLE = IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE; private Context mContext; - private BackupTransportConnection mBackupTransportConnection; + private ForensicEventTransportConnection mForensicEventTransportConnection; private DataAggregator mDataAggregator; private ForensicService mForensicService; private TestLooper mTestLooper; private Looper mLooper; private TestLooper mTestLooperOfDataAggregator; private Looper mLooperOfDataAggregator; + private FakePermissionEnforcer mPermissionEnforcer; @SuppressLint("VisibleForTests") @Before public void setUp() { mContext = spy(ApplicationProvider.getApplicationContext()); + mPermissionEnforcer = new FakePermissionEnforcer(); + mPermissionEnforcer.grant(READ_FORENSIC_STATE); + mPermissionEnforcer.grant(MANAGE_FORENSIC_STATE); + mTestLooper = new TestLooper(); mLooper = mTestLooper.getLooper(); mTestLooperOfDataAggregator = new TestLooper(); @@ -87,217 +96,101 @@ public class ForensicServiceTest { } @Test - public void testMonitorState_Invisible() throws RemoteException { + public void testAddStateCallback_NoPermission() { + mPermissionEnforcer.revoke(READ_FORENSIC_STATE); StateCallback scb = new StateCallback(); assertEquals(STATE_UNKNOWN, scb.mState); - mForensicService.getBinderService().monitorState(scb); - mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb.mState); - } - - @Test - public void testMonitorState_Invisible_TwoMonitors() throws RemoteException { - StateCallback scb1 = new StateCallback(); - assertEquals(STATE_UNKNOWN, scb1.mState); - mForensicService.getBinderService().monitorState(scb1); - mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb1.mState); - - StateCallback scb2 = new StateCallback(); - assertEquals(STATE_UNKNOWN, scb2.mState); - mForensicService.getBinderService().monitorState(scb2); - mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb2.mState); + assertThrows(SecurityException.class, + () -> mForensicService.getBinderService().addStateCallback(scb)); } @Test - public void testMakeVisible_FromInvisible() throws RemoteException { + public void testRemoveStateCallback_NoPermission() { + mPermissionEnforcer.revoke(READ_FORENSIC_STATE); StateCallback scb = new StateCallback(); assertEquals(STATE_UNKNOWN, scb.mState); - mForensicService.getBinderService().monitorState(scb); - mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb.mState); - - CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().makeVisible(ccb); - mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb.mState); - assertNull(ccb.mErrorCode); + assertThrows(SecurityException.class, + () -> mForensicService.getBinderService().removeStateCallback(scb)); } @Test - public void testMakeVisible_FromInvisible_TwoMonitors() 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(true).when(mDataAggregator).initialize(); + public void testEnable_NoPermission() { + mPermissionEnforcer.revoke(MANAGE_FORENSIC_STATE); CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().makeVisible(ccb); - mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); - assertNull(ccb.mErrorCode); + assertThrows(SecurityException.class, + () -> mForensicService.getBinderService().enable(ccb)); } @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(); + public void testDisable_NoPermission() { + mPermissionEnforcer.revoke(MANAGE_FORENSIC_STATE); 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()); + assertThrows(SecurityException.class, + () -> mForensicService.getBinderService().disable(ccb)); } @Test - public void testMakeVisible_FromVisible_TwoMonitors() throws RemoteException { - mForensicService.setState(STATE_VISIBLE); - StateCallback scb1 = new StateCallback(); - StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); - mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); - - CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().makeVisible(ccb); + public void testAddStateCallback_Disabled() throws RemoteException { + StateCallback scb = new StateCallback(); + assertEquals(STATE_UNKNOWN, scb.mState); + mForensicService.getBinderService().addStateCallback(scb); mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); - assertNull(ccb.mErrorCode); + assertEquals(STATE_DISABLED, scb.mState); } @Test - public void testMakeVisible_FromEnabled_TwoMonitors() throws RemoteException { - mForensicService.setState(STATE_ENABLED); + public void testAddStateCallback_Disabled_TwoStateCallbacks() throws RemoteException { StateCallback scb1 = new StateCallback(); - StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); - mTestLooper.dispatchAll(); - assertEquals(STATE_ENABLED, scb1.mState); - assertEquals(STATE_ENABLED, scb2.mState); - - CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().makeVisible(ccb); + assertEquals(STATE_UNKNOWN, scb1.mState); + mForensicService.getBinderService().addStateCallback(scb1); mTestLooper.dispatchAll(); - assertEquals(STATE_ENABLED, scb1.mState); - assertEquals(STATE_ENABLED, scb2.mState); - assertNotNull(ccb.mErrorCode); - assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue()); - } + assertEquals(STATE_DISABLED, scb1.mState); - @Test - public void testMakeInvisible_FromInvisible_TwoMonitors() 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); - - CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().makeInvisible(ccb); - mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb1.mState); - assertEquals(STATE_INVISIBLE, scb2.mState); - assertNull(ccb.mErrorCode); - } - - @Test - public void testMakeInvisible_FromVisible_TwoMonitors() throws RemoteException { - mForensicService.setState(STATE_VISIBLE); - StateCallback scb1 = new StateCallback(); - StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); - mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); - - CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().makeInvisible(ccb); + assertEquals(STATE_UNKNOWN, scb2.mState); + mForensicService.getBinderService().addStateCallback(scb2); mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb1.mState); - assertEquals(STATE_INVISIBLE, scb2.mState); - assertNull(ccb.mErrorCode); + assertEquals(STATE_DISABLED, scb2.mState); } @Test - public void testMakeInvisible_FromEnabled_TwoMonitors() throws RemoteException { - mForensicService.setState(STATE_ENABLED); + public void testRemoveStateCallback() throws RemoteException { + mForensicService.setState(STATE_DISABLED); StateCallback scb1 = new StateCallback(); StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); + mForensicService.getBinderService().addStateCallback(scb1); + mForensicService.getBinderService().addStateCallback(scb2); mTestLooper.dispatchAll(); - assertEquals(STATE_ENABLED, scb1.mState); - assertEquals(STATE_ENABLED, scb2.mState); + assertEquals(STATE_DISABLED, scb1.mState); + assertEquals(STATE_DISABLED, scb2.mState); - CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().makeInvisible(ccb); - mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb1.mState); - assertEquals(STATE_INVISIBLE, scb2.mState); - assertNull(ccb.mErrorCode); - } + doReturn(true).when(mDataAggregator).initialize(); + doReturn(true).when(mForensicEventTransportConnection).initialize(); - - @Test - public void testEnable_FromInvisible_TwoMonitors() 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); + mForensicService.getBinderService().removeStateCallback(scb2); CommandCallback ccb = new CommandCallback(); mForensicService.getBinderService().enable(ccb); mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb1.mState); - assertEquals(STATE_INVISIBLE, scb2.mState); - assertNotNull(ccb.mErrorCode); - assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue()); + assertEquals(STATE_ENABLED, scb1.mState); + assertEquals(STATE_DISABLED, scb2.mState); + assertNull(ccb.mErrorCode); } @Test - public void testEnable_FromVisible_TwoMonitors() throws RemoteException { - mForensicService.setState(STATE_VISIBLE); + public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException { + mForensicService.setState(STATE_DISABLED); StateCallback scb1 = new StateCallback(); StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); + mForensicService.getBinderService().addStateCallback(scb1); + mForensicService.getBinderService().addStateCallback(scb2); mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); + assertEquals(STATE_DISABLED, scb1.mState); + assertEquals(STATE_DISABLED, scb2.mState); - doReturn(true).when(mBackupTransportConnection).initialize(); + doReturn(true).when(mForensicEventTransportConnection).initialize(); CommandCallback ccb = new CommandCallback(); mForensicService.getBinderService().enable(ccb); @@ -310,35 +203,13 @@ public class ForensicServiceTest { } @Test - public void testEnable_FromVisible_TwoMonitors_BackupTransportUnavailable() + public void testEnable_FromEnabled_TwoStateCallbacks() throws RemoteException { - mForensicService.setState(STATE_VISIBLE); - StateCallback scb1 = new StateCallback(); - StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); - mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); - - doReturn(false).when(mBackupTransportConnection).initialize(); - - CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().enable(ccb); - mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); - assertNotNull(ccb.mErrorCode); - assertEquals(ERROR_BACKUP_TRANSPORT_UNAVAILABLE, ccb.mErrorCode.intValue()); - } - - @Test - public void testEnable_FromEnabled_TwoMonitors() throws RemoteException { mForensicService.setState(STATE_ENABLED); StateCallback scb1 = new StateCallback(); StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); + mForensicService.getBinderService().addStateCallback(scb1); + mForensicService.getBinderService().addStateCallback(scb2); mTestLooper.dispatchAll(); assertEquals(STATE_ENABLED, scb1.mState); assertEquals(STATE_ENABLED, scb2.mState); @@ -346,62 +217,44 @@ public class ForensicServiceTest { CommandCallback ccb = new CommandCallback(); mForensicService.getBinderService().enable(ccb); mTestLooper.dispatchAll(); + assertEquals(STATE_ENABLED, scb1.mState); assertEquals(STATE_ENABLED, scb2.mState); assertNull(ccb.mErrorCode); } @Test - public void testDisable_FromInvisible_TwoMonitors() throws RemoteException { - mForensicService.setState(STATE_INVISIBLE); + public void testDisable_FromDisabled_TwoStateCallbacks() throws RemoteException { + mForensicService.setState(STATE_DISABLED); StateCallback scb1 = new StateCallback(); StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); + mForensicService.getBinderService().addStateCallback(scb1); + mForensicService.getBinderService().addStateCallback(scb2); mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb1.mState); - assertEquals(STATE_INVISIBLE, scb2.mState); + assertEquals(STATE_DISABLED, scb1.mState); + assertEquals(STATE_DISABLED, scb2.mState); CommandCallback ccb = new CommandCallback(); mForensicService.getBinderService().disable(ccb); mTestLooper.dispatchAll(); - assertEquals(STATE_INVISIBLE, scb1.mState); - assertEquals(STATE_INVISIBLE, scb2.mState); - assertNotNull(ccb.mErrorCode); - assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue()); - } - @Test - public void testDisable_FromVisible_TwoMonitors() throws RemoteException { - mForensicService.setState(STATE_VISIBLE); - StateCallback scb1 = new StateCallback(); - StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); - mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); - - CommandCallback ccb = new CommandCallback(); - mForensicService.getBinderService().disable(ccb); - mTestLooper.dispatchAll(); - assertEquals(STATE_VISIBLE, scb1.mState); - assertEquals(STATE_VISIBLE, scb2.mState); + assertEquals(STATE_DISABLED, scb1.mState); + assertEquals(STATE_DISABLED, scb2.mState); assertNull(ccb.mErrorCode); } @Test - public void testDisable_FromEnabled_TwoMonitors() throws RemoteException { + public void testDisable_FromEnabled_TwoStateCallbacks() throws RemoteException { mForensicService.setState(STATE_ENABLED); StateCallback scb1 = new StateCallback(); StateCallback scb2 = new StateCallback(); - mForensicService.getBinderService().monitorState(scb1); - mForensicService.getBinderService().monitorState(scb2); + mForensicService.getBinderService().addStateCallback(scb1); + mForensicService.getBinderService().addStateCallback(scb2); mTestLooper.dispatchAll(); assertEquals(STATE_ENABLED, scb1.mState); assertEquals(STATE_ENABLED, scb2.mState); - doNothing().when(mBackupTransportConnection).release(); + doNothing().when(mForensicEventTransportConnection).release(); ServiceThread mockThread = spy(ServiceThread.class); mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread); @@ -412,11 +265,35 @@ public class ForensicServiceTest { 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); + assertEquals(STATE_DISABLED, scb1.mState); + assertEquals(STATE_DISABLED, scb2.mState); assertNull(ccb.mErrorCode); } + @Ignore("Enable once the ForensicEventTransportConnection is ready") + @Test + public void testEnable_FromDisable_TwoStateCallbacks_TransportUnavailable() + throws RemoteException { + mForensicService.setState(STATE_DISABLED); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().addStateCallback(scb1); + mForensicService.getBinderService().addStateCallback(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_DISABLED, scb1.mState); + assertEquals(STATE_DISABLED, scb2.mState); + + doReturn(false).when(mForensicEventTransportConnection).initialize(); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().enable(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_DISABLED, scb1.mState); + assertEquals(STATE_DISABLED, scb2.mState); + assertNotNull(ccb.mErrorCode); + assertEquals(ERROR_TRANSPORT_UNAVAILABLE, ccb.mErrorCode.intValue()); + } + @Test public void testDataAggregator_AddBatchData() { mForensicService.setState(STATE_ENABLED); @@ -441,14 +318,14 @@ public class ForensicServiceTest { events.add(eventOne); events.add(eventTwo); - doReturn(true).when(mBackupTransportConnection).addData(any()); + doReturn(true).when(mForensicEventTransportConnection).addData(any()); mDataAggregator.addBatchData(events); mTestLooperOfDataAggregator.dispatchAll(); mTestLooper.dispatchAll(); ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class); - verify(mBackupTransportConnection).addData(captor.capture()); + verify(mForensicEventTransportConnection).addData(captor.capture()); List<ForensicEvent> receivedEvents = captor.getValue(); assertEquals(receivedEvents.size(), 2); @@ -476,6 +353,10 @@ public class ForensicServiceTest { return mContext; } + @Override + public PermissionEnforcer getPermissionEnforcer() { + return mPermissionEnforcer; + } @Override public Looper getLooper() { @@ -483,9 +364,9 @@ public class ForensicServiceTest { } @Override - public BackupTransportConnection getBackupTransportConnection() { - mBackupTransportConnection = spy(new BackupTransportConnection(mContext)); - return mBackupTransportConnection; + public ForensicEventTransportConnection getForensicEventransportConnection() { + mForensicEventTransportConnection = spy(new ForensicEventTransportConnection(mContext)); + return mForensicEventTransportConnection; } @Override |